blob: e4b7fe03105844efc9e65c7ef4d042f29b411c80 [file] [log] [blame]
/*
$Id$
Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions of source code must retain copyright
statements and notices. Redistributions must also contain a
copy of this document.
2. Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. The name "groovy" must not be used to endorse or promote
products derived from this Software without prior written
permission of The Codehaus. For written permission,
please contact info@codehaus.org.
4. Products derived from this Software may not be called "groovy"
nor may "groovy" appear in their names without prior written
permission of The Codehaus. "groovy" is a registered
trademark of The Codehaus.
5. Due credit should be given to The Codehaus -
http://groovy.codehaus.org/
THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.groovy.classgen;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.VariableScope;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.AttributeExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BitwiseNegExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ExpressionTransformer;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.MethodPointerExpression;
import org.codehaus.groovy.ast.expr.NegationExpression;
import org.codehaus.groovy.ast.expr.NotExpression;
import org.codehaus.groovy.ast.expr.PostfixExpression;
import org.codehaus.groovy.ast.expr.PrefixExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.RangeExpression;
import org.codehaus.groovy.ast.expr.RegexExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import org.codehaus.groovy.ast.expr.SpreadMapExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.TernaryExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.BreakStatement;
import org.codehaus.groovy.ast.stmt.CaseStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.ContinueStatement;
import org.codehaus.groovy.ast.stmt.DoWhileStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.SwitchStatement;
import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
import org.codehaus.groovy.ast.stmt.ThrowStatement;
import org.codehaus.groovy.ast.stmt.TryCatchStatement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.Types;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* 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>
*
* @version $Revision$
*/
public class AsmClassGenerator extends ClassGenerator {
private Logger log = Logger.getLogger(getClass().getName());
private ClassVisitor cw;
private MethodVisitor cv;
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 */
private boolean leftHandExpression=false;
/**
* Notes for leftHandExpression:
* The default is false, that menas 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.
*/
// method invocation
MethodCallerMultiAdapter invokeMethodOnCurrent = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"invokeMethodOnCurrent",true,false);
MethodCallerMultiAdapter invokeMethodOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"invokeMethodOnSuper",true,false);
MethodCallerMultiAdapter invokeMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"invokeMethod",true,false);
MethodCallerMultiAdapter invokeStaticMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"invokeStaticMethod",true,true);
MethodCallerMultiAdapter invokeNew = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"invokeNew",true,true);
// fields & properties
MethodCallerMultiAdapter setField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setField",false,false);
MethodCallerMultiAdapter getField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getField",false,false);
MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setGroovyObjectField",false,false);
MethodCallerMultiAdapter getGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getGroovyObjectField",false,false);
MethodCallerMultiAdapter setFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setFieldOnSuper",false,false);
MethodCallerMultiAdapter getFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getFieldOnSuper",false,false);
MethodCallerMultiAdapter setProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setProperty",false,false);
MethodCallerMultiAdapter getProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getProperty",false,false);
MethodCallerMultiAdapter setGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setGroovyObjectProperty",false,false);
MethodCallerMultiAdapter getGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getGroovyObjectProperty",false,false);
MethodCallerMultiAdapter setPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"setPropertyOnSuper",false,false);
MethodCallerMultiAdapter getPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class,"getPropertyOnSuper",false,false);
// iterator
MethodCaller iteratorNextMethod = MethodCaller.newInterface(Iterator.class, "next");
MethodCaller iteratorHasNextMethod = MethodCaller.newInterface(Iterator.class, "hasNext");
// assert
MethodCaller assertFailedMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "assertFailed");
// isCase
MethodCaller isCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isCase");
//compare
MethodCaller compareIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareIdentical");
MethodCaller compareEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareEqual");
MethodCaller compareNotEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotEqual");
MethodCaller compareToMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareTo");
MethodCaller compareLessThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThan");
MethodCaller compareLessThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThanEqual");
MethodCaller compareGreaterThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThan");
MethodCaller compareGreaterThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThanEqual");
//regexpr
MethodCaller findRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "findRegex");
MethodCaller matchRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "matchRegex");
MethodCaller regexPattern = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "regexPattern");
// spread expressions
MethodCaller spreadMap = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "spreadMap");
MethodCaller despreadList = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "despreadList");
// Closure
MethodCaller getMethodPointer = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "getMethodPointer");
MethodCaller invokeClosureMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "invokeClosure");
//negation
MethodCaller negation = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "negate");
MethodCaller bitNegation = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "bitNegate");
// type converions
MethodCaller asTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "asType");
MethodCaller castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType");
MethodCaller createListMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createList");
MethodCaller createTupleMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createTuple");
MethodCaller createMapMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createMap");
MethodCaller createRangeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createRange");
// wrapper creation methods
MethodCaller createPojoWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createPojoWrapper");
MethodCaller createGroovyObjectWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createGroovyObjectWrapper");
// constructor calls with this() and super()
MethodCaller selectConstructorAndTransformArguments = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "selectConstructorAndTransformArguments");
// exception blocks list
private List exceptionBlocks = new ArrayList();
private Set syntheticStaticFields = new HashSet();
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;
public AsmClassGenerator(
GeneratorContext context, ClassVisitor classVisitor,
ClassLoader classLoader, String sourceFile
) {
super(classLoader);
this.context = context;
this.cw = classVisitor;
this.sourceFile = sourceFile;
this.dummyClassWriter = new ClassWriter(true);
dummyGen = new DummyClassGenerator(context, dummyClassWriter, classLoader, sourceFile);
compileStack = new CompileStack();
}
protected SourceUnit getSourceUnit() {
return null;
}
// GroovyClassVisitor interface
//-------------------------------------------------------------------------
public void visitClass(ClassNode classNode) {
// todo to be tested
// createDummyClass(classNode);
try {
syntheticStaticFields.clear();
this.classNode = classNode;
this.outermostClass = null;
this.internalClassName = BytecodeHelper.getClassInternalName(classNode);
this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass());
cw.visit(
asmJDKVersion,
classNode.getModifiers(),
internalClassName,
null,
internalBaseClassName,
BytecodeHelper.getClassInternalNames(classNode.getInterfaces())
);
cw.visitSource(sourceFile,null);
if (classNode.isInterface()) {
ClassNode owner = classNode;
if (owner instanceof InnerClassNode) {
owner = owner.getOuterClass();
}
String outerClassName = owner.getName();
String name = outerClassName + "$" + context.getNextInnerClassIdx();
interfaceClassLoadingClass = new InnerClassNode(owner, name, 4128, ClassHelper.OBJECT_TYPE);
super.visitClass(classNode);
createInterfaceSyntheticStaticFields();
} else {
super.visitClass(classNode);
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;
}
cw.visitInnerClass(
innerClassInternalName,
outerClassName,
innerClassName,
innerClass.getModifiers());
}
//TODO: an inner class should have an entry of itself
cw.visitEnd();
}
catch (GroovyRuntimeException e) {
e.setModule(classNode.getModule());
throw e;
}
}
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
*
* @see #generateMopCalls(LinkedList, boolean)
*
* @param methods unfiltered list of methods for MOP
* @param isThis if true, then we are creating a MOP method on "this", "super" else
*/
private void visitMopMethodList(List methods, boolean isThis){
LinkedList mopCalls = new LinkedList();
for (Iterator iter = methods.iterator(); iter.hasNext();) {
MethodNode mn = (MethodNode) iter.next();
if ((mn.getModifiers() & ACC_ABSTRACT) !=0 ) 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) || methodName.startsWith("<")) continue;
String name = getMopMethodName(mn,isThis);
if (containsMethod(methods,name,mn.getParameters())) continue;
mopCalls.add(mn);
}
generateMopCalls(mopCalls, isThis);
mopCalls.clear();
}
private boolean containsMethod(List methods, String name, Parameter[] paras) {
for (Iterator iter = methods.iterator(); iter.hasNext();) {
MethodNode element = (MethodNode) iter.next();
if (element.getName().equals(name) && equalParameterTypes(paras,element.getParameters())) return true;
}
return false;
}
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 Protocoll 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());
cv = cw.visitMethod(Opcodes.ACC_PUBLIC & Opcodes.ACC_SYNTHETIC, name, methodDescriptor, null, null);
cv.visitVarInsn(ALOAD,0);
BytecodeHelper helper = new BytecodeHelper(cv);
int newRegister = 1;
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++;
}
cv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(method.getDeclaringClass()), method.getName(), methodDescriptor);
helper.doReturn(method.getReturnType());
cv.visitMaxs(0, 0);
cv.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) {
String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), node.getParameters());
cv = cw.visitMethod(node.getModifiers(), node.getName(), methodType, null, buildExceptions(node.getExceptions()));
helper = new BytecodeHelper(cv);
if (!node.isAbstract()) {
Statement code = node.getCode();
if (isConstructor && (code == null || !firstStatementIsSpecialConstructorCall(node))) {
// invokes the super class constructor
cv.visitVarInsn(ALOAD, 0);
cv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(classNode.getSuperClass()), "<init>", "()V");
}
compileStack.init(node.getVariableScope(),node.getParameters(),cv, classNode);
// ensure we save the current (meta) class in a register
(new ClassExpression(classNode)).visit(this);
cv.visitInsn(POP);
(new ClassExpression(ClassHelper.METACLASS_TYPE)).visit(this);
cv.visitInsn(POP);
// handle body
super.visitConstructorOrMethod(node, isConstructor);
if (!outputReturn || node.isVoidMethod()) {
cv.visitInsn(RETURN);
}
compileStack.clear();
// lets do all the exception blocks
for (Iterator iter = exceptionBlocks.iterator(); iter.hasNext();) {
Runnable runnable = (Runnable) iter.next();
runnable.run();
}
exceptionBlocks.clear();
cv.visitMaxs(0, 0);
}
}
private boolean firstStatementIsSpecialConstructorCall(MethodNode node) {
Statement code = node.getFirstStatement();
if (code == null || !(code instanceof ExpressionStatement)) return false;
Expression expression = ((ExpressionStatement)code).getExpression();
if (!(expression instanceof ConstructorCallExpression)) return false;
ConstructorCallExpression cce = (ConstructorCallExpression) expression;
return cce.isSpecialCall();
}
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();
cw.visitField(
fieldNode.getModifiers(),
fieldNode.getName(),
BytecodeHelper.getTypeDescription(t),
null, //fieldValue, //br all the sudden that one cannot init the field here. init is done in static initilizer and instace intializer.
null);
visitAnnotations(fieldNode);
}
public void visitProperty(PropertyNode statement) {
// the verifyer 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);
cv.visitLabel(label);
}
}
public void visitBlockStatement(BlockStatement block) {
onLineNumber(block, "visitBlockStatement");
visitStatement(block);
compileStack.pushVariableScope(block.getVariableScope());
super.visitBlockStatement(block);
compileStack.pop();
}
public void visitForLoop(ForStatement loop) {
onLineNumber(loop, "visitForLoop");
visitStatement(loop);
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();
cv.visitLabel(continueLabel);
cv.visitVarInsn(ALOAD, iteratorIdx);
iteratorHasNextMethod.call(cv);
// note: ifeq tests for ==0, a boolean is 0 if it is false
cv.visitJumpInsn(IFEQ, breakLabel);
cv.visitVarInsn(ALOAD, iteratorIdx);
iteratorNextMethod.call(cv);
helper.storeVar(variable);
// Generate the loop body
loop.getLoopBlock().visit(this);
cv.visitJumpInsn(GOTO, continueLabel);
cv.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();
cv.visitLabel(continueLabel);
loop.getBooleanExpression().visit(this);
cv.visitJumpInsn(IFEQ, breakLabel);
loop.getLoopBlock().visit(this);
cv.visitJumpInsn(GOTO, continueLabel);
cv.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();
cv.visitLabel(continueLabel);
loop.getLoopBlock().visit(this);
loop.getBooleanExpression().visit(this);
cv.visitJumpInsn(IFEQ, continueLabel);
cv.visitLabel(breakLabel);
compileStack.pop();
}
public void visitIfElse(IfStatement ifElse) {
onLineNumber(ifElse, "visitIfElse");
visitStatement(ifElse);
ifElse.getBooleanExpression().visit(this);
Label l0 = new Label();
cv.visitJumpInsn(IFEQ, l0);
ifElse.getIfBlock().visit(this);
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
ifElse.getElseBlock().visit(this);
cv.visitLabel(l1);
}
public void visitTernaryExpression(TernaryExpression expression) {
onLineNumber(expression, "visitTernaryExpression");
expression.getBooleanExpression().visit(this);
Label l0 = new Label();
cv.visitJumpInsn(IFEQ, l0);
visitAndAutoboxBoolean(expression.getTrueExpression());
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
visitAndAutoboxBoolean(expression.getFalseExpression());
cv.visitLabel(l1);
}
public void visitAssertStatement(AssertStatement statement) {
onLineNumber(statement, "visitAssertStatement");
visitStatement(statement);
BooleanExpression booleanExpression = statement.getBooleanExpression();
booleanExpression.visit(this);
Label l0 = new Label();
cv.visitJumpInsn(IFEQ, l0);
// do nothing
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
// push expression string onto stack
String expressionText = booleanExpression.getText();
List list = new ArrayList();
addVariableNames(booleanExpression, list);
if (list.isEmpty()) {
cv.visitLdcInsn(expressionText);
}
else {
boolean first = true;
// lets create a new expression
cv.visitTypeInsn(NEW, "java/lang/StringBuffer");
cv.visitInsn(DUP);
cv.visitLdcInsn(expressionText + ". Values: ");
cv.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;
}
cv.visitVarInsn(ALOAD, tempIndex);
cv.visitLdcInsn(text);
cv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuffer",
"append",
"(Ljava/lang/Object;)Ljava/lang/StringBuffer;");
cv.visitInsn(POP);
cv.visitVarInsn(ALOAD, tempIndex);
new VariableExpression(name).visit(this);
cv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuffer",
"append",
"(Ljava/lang/Object;)Ljava/lang/StringBuffer;");
cv.visitInsn(POP);
}
cv.visitVarInsn(ALOAD, tempIndex);
compileStack.removeVar(tempIndex);
}
// now the optional exception expression
statement.getMessageExpression().visit(this);
assertFailedMethod.call(cv);
cv.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(){finallyStatement.visit(AsmClassGenerator.this);}
}
);
}
// start try block, label needed for exception table
final Label tryStart = new Label();
cv.visitLabel(tryStart);
tryStatement.visit(this);
// goto finally part
final Label finallyStart = new Label();
cv.visitJumpInsn(GOTO, finallyStart);
// marker needed for Exception table
final Label tryEnd = new Label();
cv.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();
cv.visitLabel(catchStart);
// create exception variable and store the exception
compileStack.defineVariable(catchStatement.getVariable(),true);
// handle catch body
catchStatement.visit(this);
// goto finally start
cv.visitJumpInsn(GOTO, finallyStart);
// add exception to table
final String exceptionTypeInternalName = BytecodeHelper.getClassInternalName(exceptionType);
exceptionBlocks.add(new Runnable() {
public void run() {
cv.visitTryCatchBlock(tryStart, tryEnd, catchStart, exceptionTypeInternalName);
}
});
}
// marker needed for the exception table
final Label endOfAllCatches = new Label();
cv.visitLabel(endOfAllCatches);
// remove the finally, don't let it visit itself
if (!finallyStatement.isEmpty()) compileStack.popFinallyBlock();
// start finally
cv.visitLabel(finallyStart);
finallyStatement.visit(this);
// goto end of finally
Label afterFinally = new Label();
cv.visitJumpInsn(GOTO, afterFinally);
// start a block catching any Exception
final Label catchAny = new Label();
cv.visitLabel(catchAny);
//store exception
cv.visitVarInsn(ASTORE, anyExceptionIndex);
finallyStatement.visit(this);
// load the exception and rethrow it
cv.visitVarInsn(ALOAD, anyExceptionIndex);
cv.visitInsn(ATHROW);
// end of all catches and finally parts
cv.visitLabel(afterFinally);
// add catch any block to exception table
exceptionBlocks.add(new Runnable() {
public void run() {
cv.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);
cv.visitLabel(breakLabel);
compileStack.pop();
}
public void visitCaseStatement(CaseStatement statement) {
}
public void visitCaseStatement(
CaseStatement statement,
int switchVariableIndex,
Label thisLabel,
Label nextLabel) {
onLineNumber(statement, "visitCaseStatement");
cv.visitVarInsn(ALOAD, switchVariableIndex);
statement.getExpression().visit(this);
isCaseMethod.call(cv);
Label l0 = new Label();
cv.visitJumpInsn(IFEQ, l0);
cv.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) {
cv.visitJumpInsn(GOTO, nextLabel);
}
cv.visitLabel(l0);
}
public void visitBreakStatement(BreakStatement statement) {
onLineNumber(statement, "visitBreakStatement");
visitStatement(statement);
String name = statement.getLabel();
Label breakLabel = compileStack.getNamedBreakLabel(name);
compileStack.applyFinallyBlocks(breakLabel, true);
cv.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);
cv.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();
cv.visitVarInsn(ALOAD, index);
cv.visitInsn(MONITORENTER);
cv.visitLabel(synchronizedStart);
Runnable finallyPart = new Runnable(){
public void run(){
cv.visitVarInsn(ALOAD, index);
cv.visitInsn(MONITOREXIT);
}
};
compileStack.pushFinallyBlock(finallyPart);
statement.getCode().visit(this);
finallyPart.run();
cv.visitJumpInsn(GOTO, synchronizedEnd);
cv.visitLabel(catchAll);
finallyPart.run();
cv.visitInsn(ATHROW);
cv.visitLabel(synchronizedEnd);
compileStack.popFinallyBlock();
exceptionBlocks.add(new Runnable() {
public void run() {
cv.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
cv.visitTypeInsn(CHECKCAST, "java/lang/Throwable");
cv.visitInsn(ATHROW);
}
public void visitReturnStatement(ReturnStatement statement) {
onLineNumber(statement, "visitReturnStatement");
visitStatement(statement);
ClassNode returnType = methodNode.getReturnType();
if (returnType==ClassHelper.VOID_TYPE) {
if (!(statement == ReturnStatement.RETURN_NULL_OR_VOID)) {
throwException("Cannot use return statement with an expression on a method that returns void");
}
compileStack.applyFinallyBlocks();
cv.visitInsn(RETURN);
outputReturn = true;
return;
}
Expression expression = statement.getExpression();
evaluateExpression(expression);
if (returnType==ClassHelper.OBJECT_TYPE && expression.getType() != null && expression.getType()==ClassHelper.VOID_TYPE) {
cv.visitInsn(ACONST_NULL); // cheat the caller
} else {
// return is based on class type
// we may need to cast
doConvertAndCast(returnType, expression, false, true, false);
helper.unbox(returnType);
}
if (compileStack.hasFinallyBlocks()) {
int returnValueIdx = compileStack.defineTemporaryVariable("returnValue",returnType,true);
compileStack.applyFinallyBlocks();
helper.load(returnType,returnValueIdx);
}
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 corresponsing Object type
if (!ignoreAutoboxing && ClassHelper.isPrimitiveType(type)) {
type = ClassHelper.getWrapper(type);
}
if (forceCast || (type!=null && !type.equals(expType))) {
doConvertAndCast(type,coerce);
}
}
/**
* @param expression
*/
protected void evaluateExpression(Expression expression) {
visitAndAutoboxBoolean(expression);
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)) {
cv.visitInsn(POP);
}
}
// Expressions
//-------------------------------------------------------------------------
public void visitDeclarationExpression(DeclarationExpression expression) {
onLineNumber(expression, "visitDeclarationExpression: \""+expression.getVariableExpression().getName()+"\"");
Expression rightExpression = expression.getRightExpression();
// no need to visit left side, just get the variable name
VariableExpression vex = expression.getVariableExpression();
ClassNode type = vex.getType();
// lets not cast for primitive types as we handle these in field setting etc
if (ClassHelper.isPrimitiveType(type)) {
rightExpression.visit(this);
} else {
if (type!=ClassHelper.OBJECT_TYPE){
visitCastExpression(new CastExpression(type, rightExpression));
} else {
visitAndAutoboxBoolean(rightExpression);
}
}
compileStack.defineVariable(vex,true);
}
public void visitBinaryExpression(BinaryExpression expression) {
onLineNumber(expression, "visitBinaryExpression: \"" + expression.getOperation().getText() + "\" ");
switch (expression.getOperation().getType()) {
case Types.EQUAL : // = assignment
evaluateEqual(expression);
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 :
evaluateBinaryExpressionWithAsignment("and", expression);
break;
case Types.BITWISE_OR :
evaluateBinaryExpression("or", expression);
break;
case Types.BITWISE_OR_EQUAL :
evaluateBinaryExpressionWithAsignment("or", expression);
break;
case Types.BITWISE_XOR :
evaluateBinaryExpression("xor", expression);
break;
case Types.BITWISE_XOR_EQUAL :
evaluateBinaryExpressionWithAsignment("xor", expression);
break;
case Types.PLUS :
evaluateBinaryExpression("plus", expression);
break;
case Types.PLUS_EQUAL :
evaluateBinaryExpressionWithAsignment("plus", expression);
break;
case Types.MINUS :
evaluateBinaryExpression("minus", expression);
break;
case Types.MINUS_EQUAL :
evaluateBinaryExpressionWithAsignment("minus", expression);
break;
case Types.MULTIPLY :
evaluateBinaryExpression("multiply", expression);
break;
case Types.MULTIPLY_EQUAL :
evaluateBinaryExpressionWithAsignment("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
evaluateBinaryExpressionWithAsignment("div", expression);
break;
case Types.INTDIV :
evaluateBinaryExpression("intdiv", expression);
break;
case Types.INTDIV_EQUAL :
evaluateBinaryExpressionWithAsignment("intdiv", expression);
break;
case Types.MOD :
evaluateBinaryExpression("mod", expression);
break;
case Types.MOD_EQUAL :
evaluateBinaryExpressionWithAsignment("mod", expression);
break;
case Types.POWER :
evaluateBinaryExpression("power", expression);
break;
case Types.POWER_EQUAL :
evaluateBinaryExpressionWithAsignment("power", expression);
break;
case Types.LEFT_SHIFT :
evaluateBinaryExpression("leftShift", expression);
break;
case Types.LEFT_SHIFT_EQUAL :
evaluateBinaryExpressionWithAsignment("leftShift", expression);
break;
case Types.RIGHT_SHIFT :
evaluateBinaryExpression("rightShift", expression);
break;
case Types.RIGHT_SHIFT_EQUAL :
evaluateBinaryExpressionWithAsignment("rightShift", expression);
break;
case Types.RIGHT_SHIFT_UNSIGNED :
evaluateBinaryExpression("rightShiftUnsigned", expression);
break;
case Types.RIGHT_SHIFT_UNSIGNED_EQUAL :
evaluateBinaryExpressionWithAsignment("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 = createClosureClass(expression);
addInnerClass(innerClass);
String innerClassinternalName = BytecodeHelper.getClassInternalName(innerClass);
passingClosureParams = true;
List constructors = innerClass.getDeclaredConstructors();
ConstructorNode node = (ConstructorNode) constructors.get(0);
Parameter[] localVariableParams = node.getParameters();
cv.visitTypeInsn(NEW, innerClassinternalName);
cv.visitInsn(DUP);
if (isStaticMethod() || classNode.isStaticClass()) {
visitClassExpression(new ClassExpression(classNode));
visitClassExpression(new ClassExpression(getOutermostClass()));
} else {
cv.visitVarInsn(ALOAD, 0);
loadThis();
}
// now lets 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.getField(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.getField(name);
cv.visitVarInsn(ALOAD, 0);
cv.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);
}
cv.visitVarInsn(ALOAD, v.getIndex());
}
}
passingClosureParams = false;
// we may need to pass in some other constructors
//cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V");
cv.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.getField("owner")));
} else {
loadThis();
}
}
public void visitRegexExpression(RegexExpression expression) {
expression.getRegex().visit(this);
regexPattern.call(cv);
}
/**
* 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) {
Object value = expression.getValue();
helper.loadConstant(value);
}
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(cv);
}
public void visitMethodPointerExpression(MethodPointerExpression expression) {
Expression subExpression = expression.getExpression();
subExpression.visit(this);
helper.loadConstant(expression.getMethodName());
getMethodPointer.call(cv);
}
public void visitNegationExpression(NegationExpression expression) {
Expression subExpression = expression.getExpression();
subExpression.visit(this);
negation.call(cv);
}
public void visitBitwiseNegExpression(BitwiseNegExpression expression) {
Expression subExpression = expression.getExpression();
subExpression.visit(this);
bitNegation.call(cv);
}
public void visitCastExpression(CastExpression expression) {
ClassNode type = expression.getType();
visitAndAutoboxBoolean(expression.getExpression());
doConvertAndCast(type, expression.getExpression(), expression.isIgnoringAutoboxing(),false,expression.isCoerce());
}
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(ClassHelper.make(GroovyObject.class),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
) {
// ensure VariableArguments are read, not stored
boolean lhs = leftHandExpression;
leftHandExpression = false;
// sender
sender.visit(this);
// receiver
boolean oldVal = this.implicitThis;
this.implicitThis = implicitThis;
receiver.visit(this);
this.implicitThis = oldVal;
// message
if (message!=null) message.visit(this);
// arguments
boolean containsSpreadExpression = containsSpreadExpression(arguments);
int numberOfArguments = containsSpreadExpression?-1:argumentSize(arguments);
if (numberOfArguments > adapter.maxArgs || 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(cv,numberOfArguments,safe,spreadSafe);
leftHandExpression = lhs;
}
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(new Integer(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(cv);
}
public void visitMethodCallExpression(MethodCallExpression call) {
onLineNumber(call, "visitMethodCallExpression: \"" + call.getMethod() + "\":");
Expression arguments = call.getArguments();
String methodName = call.getMethodAsString();
boolean isSuperMethodCall = usesSuper(call);
boolean isThisExpression = isThisExpression(call.getObjectExpression());
// are we a local variable
if (methodName!=null && isThisExpression && isFieldOrVariable(methodName) && ! classNode.hasPossibleMethod(methodName, arguments)) {
// lets invoke the closure method
visitVariableExpression(new VariableExpression(methodName));
arguments.visit(this);
invokeClosureMethod.call(cv);
} else {
MethodCallerMultiAdapter adapter = invokeMethod;
if (isThisExpression) adapter = invokeMethodOnCurrent;
if (isSuperMethodCall) adapter = invokeMethodOnSuper;
if (isStaticInvocation(call)) adapter = invokeStaticMethod;
makeInvokeMethodCall(call,isSuperMethodCall,adapter);
}
}
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 visitSpecialConstructorCall(ConstructorCallExpression call) {
ClassNode callNode = classNode;
if (call.isSuperCall()) callNode = callNode.getSuperClass();
List constructors = sortConstructors(call, callNode);
call.getArguments().visit(this);
// keep Objet[] on stack
cv.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 construtcor number
selectConstructorAndTransformArguments.call(cv);
// Object[],int -> int,Object[],int
// we need to examine the flags and maybe change the
// Object[] later, so this reordering will do the job
cv.visitInsn(DUP_X1);
// test if rewrap flag is set
cv.visitInsn(ICONST_1);
cv.visitInsn(IAND);
Label afterIf = new Label();
cv.visitJumpInsn(IFEQ, afterIf);
// true part, so rewrap using the first argument
cv.visitInsn(ICONST_0);
cv.visitInsn(AALOAD);
cv.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;");
cv.visitLabel(afterIf);
// here the stack is int,Object[], but we need the
// the int for our table, so swap it
cv.visitInsn(SWAP);
//load "this"
cv.visitVarInsn(ALOAD, 0);
cv.visitInsn(SWAP);
//prepare switch with >>8
cv.visitIntInsn(BIPUSH,8);
cv.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();
cv.visitLookupSwitchInsn(defaultLabel, indices, targets);
for (int i=0; i<targets.length; i++) {
cv.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. 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[]
cv.visitInsn(SWAP);
cv.visitInsn(DUP_X1);
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++) {
cv.visitInsn(DUP);
helper.pushConstant(p);
cv.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[]
cv.visitInsn(POP);
// make the constructor call
cv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor);
cv.visitJumpInsn(GOTO, afterSwitch);
}
cv.visitLabel(defaultLabel);
// this part should never be reached!
cv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
cv.visitInsn(DUP);
cv.visitLdcInsn("illegal constructor number");
cv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
cv.visitInsn(ATHROW);
cv.visitLabel(afterSwitch);
}
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()){
visitSpecialConstructorCall(call);
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());
makeCall(
receiverClass, null,
arguments,
invokeNew, 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 (isThisExpression(objectExpression)) {
// lets use the field expression if its available
String name = expression.getPropertyAsString();
if (name!=null) {
FieldNode field = classNode.getField(name);
if (field != null) {
visitFieldExpression(new FieldExpression(field));
return;
}
}
}
// arguments already on stack if any
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 (!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 = getFieldOnSuper;
} else {
adapter = getField;
if (isGroovyObject(objectExpression)) adapter = getGroovyObjectField;
if (usesSuper(expression)) adapter = getFieldOnSuper;
}
visitAttributeOrProperty(expression,adapter);
}
protected boolean isGroovyObject(Expression objectExpression) {
return isThisExpression(objectExpression);
}
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) {
cv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type));
cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
}
else {
cv.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());
cv.visitVarInsn(ALOAD, 0);
cv.visitFieldInsn(GETFIELD, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type));
if (holder) {
cv.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) {
cv.visitVarInsn(ALOAD, 0);
cv.visitFieldInsn(GETFIELD, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type));
cv.visitInsn(SWAP);
cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
}
else {
if (isInClosureConstructor()) {
helper.doCast(type);
} else if (!ClassHelper.isPrimitiveType(type)){
doConvertAndCast(type);
}
cv.visitVarInsn(ALOAD, 0);
//helper.swapObjectWith(type);
cv.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) {
cv.visitFieldInsn(GETSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type));
cv.visitInsn(SWAP);
cv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
} else {
helper.doCast(type);
cv.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) {
cv.visitVarInsn(ALOAD, 0);
cv.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) {
cv.visitVarInsn(ALOAD, tempIdx);
boolean holder = field.isHolder() && !isInClosureConstructor();
if ( !holder) {
doConvertAndCast(field.getType());
}
}
cv.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() {
cv.visitVarInsn(ALOAD, 0);
if (!implicitThis && isInClosure()) {
cv.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() ) {
// lets create a ScriptReference to pass into the closure
cv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/ScriptReference");
cv.visitInsn(DUP);
loadThisOrOwner();
cv.visitLdcInsn(name);
cv.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) {
if (expression.getType()==ClassHelper.VOID_TYPE) { // nothing on the stack
return false;
} else {
return !usesSuper((MethodCallExpression) expression);
}
}
if (expression instanceof DeclarationExpression) {
return false;
}
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 assignemnt.
// 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 evaluateBinaryExpressionWithAsignment()
// 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 (syntheticStaticFields.isEmpty()) return;
addInnerClass(interfaceClassLoadingClass);
for (Iterator iter = syntheticStaticFields.iterator(); iter.hasNext();) {
String staticFieldName = (String) iter.next();
// generate a field node
interfaceClassLoadingClass.addField(staticFieldName,ACC_STATIC + ACC_SYNTHETIC,ClassHelper.CLASS_Type,null);
}
}
protected void createSyntheticStaticFields() {
for (Iterator iter = syntheticStaticFields.iterator(); iter.hasNext();) {
String staticFieldName = (String) iter.next();
// generate a field node
FieldNode fn = classNode.getField(staticFieldName);
if (fn!=null) {
boolean type = fn.getType()==ClassHelper.CLASS_Type;
boolean modifiers = fn.getModifiers() == ACC_STATIC + ACC_SYNTHETIC;
if (type && modifiers) continue;
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 {
cw.visitField(ACC_STATIC + ACC_SYNTHETIC, staticFieldName, "Ljava/lang/Class;", null, null);
}
}
cv =
cw.visitMethod(
ACC_STATIC + ACC_SYNTHETIC,
"class$",
"(Ljava/lang/String;)Ljava/lang/Class;",
null,
null);
Label l0 = new Label();
cv.visitLabel(l0);
cv.visitVarInsn(ALOAD, 0);
cv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
Label l1 = new Label();
cv.visitLabel(l1);
cv.visitInsn(ARETURN);
Label l2 = new Label();
cv.visitLabel(l2);
cv.visitVarInsn(ASTORE, 1);
cv.visitTypeInsn(NEW, "java/lang/NoClassDefFoundError");
cv.visitInsn(DUP);
cv.visitVarInsn(ALOAD, 1);
cv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassNotFoundException", "getMessage", "()Ljava/lang/String;");
cv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V");
cv.visitInsn(ATHROW);
cv.visitTryCatchBlock(l0, l2, l2, "java/lang/ClassNotFoundException"); // br using l2 as the 2nd param seems create the right table entry
cv.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);
cv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(objectType), "TYPE", "Ljava/lang/Class;");
} else {
String staticFieldName;
if (type.equals(classNode)) {
staticFieldName = "class$0";
if (compileStack.getCurrentClassIndex()!=-1) {
cv.visitVarInsn(ALOAD,compileStack.getCurrentClassIndex());
return;
}
} else if (type.equals(ClassHelper.METACLASS_TYPE)) {
staticFieldName = getStaticFieldName(type);
if (compileStack.getCurrentMetaClassIndex()!=-1) {
cv.visitVarInsn(ALOAD,compileStack.getCurrentMetaClassIndex());
return;
}
} else {
staticFieldName = getStaticFieldName(type);
}
syntheticStaticFields.add(staticFieldName);
String internalClassName = this.internalClassName;
if (classNode.isInterface()) {
internalClassName = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass);
}
cv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
Label l0 = new Label();
cv.visitJumpInsn(IFNONNULL, l0);
cv.visitLdcInsn(BytecodeHelper.getClassLoadingTypeDescription(type));
cv.visitMethodInsn(INVOKESTATIC, internalClassName, "class$", "(Ljava/lang/String;)Ljava/lang/Class;");
cv.visitInsn(DUP);
cv.visitFieldInsn(PUTSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
cv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
cv.visitLabel(l1);
if (type.equals(classNode)) {
cv.visitInsn(DUP);
int index = compileStack.defineTemporaryVariable("class$0",ClassHelper.CLASS_Type,true);
compileStack.setCurrentClassIndex(index);
} else if (type.equals(ClassHelper.METACLASS_TYPE)) {
cv.visitInsn(DUP);
int index = compileStack.defineTemporaryVariable("meta$class$0",ClassHelper.CLASS_Type,true);
compileStack.setCurrentMetaClassIndex(index);
}
}
}
public void visitRangeExpression(RangeExpression expression) {
expression.getFrom().visit(this);
expression.getTo().visit(this);
helper.pushConstant(expression.isInclusive());
createRangeMethod.call(cv);
}
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);
cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
int i = 0;
for (Iterator iter = entries.iterator(); iter.hasNext();) {
Object object = iter.next();
MapEntryExpression entry = (MapEntryExpression) object;
cv.visitInsn(DUP);
helper.pushConstant(i++);
visitAndAutoboxBoolean(entry.getKeyExpression());
cv.visitInsn(AASTORE);
cv.visitInsn(DUP);
helper.pushConstant(i++);
visitAndAutoboxBoolean(entry.getValueExpression());
cv.visitInsn(AASTORE);
}
createMapMethod.call(cv);
}
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);
cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int i = 0; i < size; i++) {
cv.visitInsn(DUP);
helper.pushConstant(i);
Expression argument = expression.getExpression(i);
visitAndAutoboxBoolean(argument);
if (useWrapper && argument instanceof CastExpression) loadWrapper(argument);
cv.visitInsn(AASTORE);
}
}
private void loadWrapper(Expression argument) {
ClassNode goalClass = argument.getType();
visitClassExpression(new ClassExpression(goalClass));
if (goalClass.isDerivedFromGroovyObject()) {
createGroovyObjectWrapperMethod.call(cv);
} else {
createPojoWrapperMethod.call(cv);
}
}
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++;
// lets 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());
cv.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;
}
cv.visitIntInsn(NEWARRAY, primType);
} else {
cv.visitTypeInsn(ANEWARRAY, arrayTypeName);
}
for (int i = 0; i < size; i++) {
cv.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);
}
}
cv.visitInsn(storeIns);
}
if (sizeExpression==null && ClassHelper.isPrimitiveType(elementType)) {
int par = compileStack.defineTemporaryVariable("par",true);
cv.visitVarInsn(ALOAD, par);
}
}
public void visitListExpression(ListExpression expression) {
int size = expression.getExpressions().size();
boolean containsSpreadExpression = containsSpreadExpression(expression);
if (!containsSpreadExpression) {
helper.pushConstant(size);
cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int i = 0; i < size; i++) {
cv.visitInsn(DUP);
helper.pushConstant(i);
visitAndAutoboxBoolean(expression.getExpression(i));
cv.visitInsn(AASTORE);
}
} else {
despreadList(expression.getExpressions(),false);
}
createListMethod.call(cv);
}
public void visitGStringExpression(GStringExpression expression) {
int size = expression.getValues().size();
helper.pushConstant(size);
cv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int i = 0; i < size; i++) {
cv.visitInsn(DUP);
helper.pushConstant(i);
visitAndAutoboxBoolean(expression.getValue(i));
cv.visitInsn(AASTORE);
}
int paramIdx = compileStack.defineTemporaryVariable("iterator",true);
ClassNode innerClass = createGStringClass(expression);
addInnerClass(innerClass);
String innerClassinternalName = BytecodeHelper.getClassInternalName(innerClass);
cv.visitTypeInsn(NEW, innerClassinternalName);
cv.visitInsn(DUP);
cv.visitVarInsn(ALOAD, paramIdx);
cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", "([Ljava/lang/Object;)V");
compileStack.removeVar(paramIdx);
}
public void visitAnnotations(AnnotatedNode node) {
Map annotionMap = node.getAnnotations();
if (annotionMap.isEmpty()) return;
Iterator it = annotionMap.values().iterator();
while (it.hasNext()) {
AnnotationNode an = (AnnotationNode) it.next();
//skip builtin properties
if (an.isBuiltIn()) continue;
ClassNode type = an.getClassNode();
String clazz = type.getName();
AnnotationVisitor av = cw.visitAnnotation(BytecodeHelper.formatNameForClassLoading(clazz),false);
Iterator mIt = an.getMembers().keySet().iterator();
while (mIt.hasNext()) {
String name = (String) mIt.next();
ConstantExpression exp = (ConstantExpression) an.getMember(name);
av.visit(name,exp.getValue());
}
av.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); // br added a more infomative name
boolean staticMethodOrInStaticClass = isStaticMethod() || classNode.isStaticClass();
Parameter[] parameters = expression.getParameters();
if (parameters==null){
parameters = new Parameter[0];
} else if (parameters.length == 0) {
// lets create a default 'it' parameter
parameters = new Parameter[] { new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL)};
}
Parameter[] localVariableParams = getClosureSharedVariables(expression);
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)) {
// lets 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);
}
// lets make the constructor
BlockStatement block = new BlockStatement();
block.setSourcePosition(expression);
VariableExpression outer = new VariableExpression("_outerInstance");
outer.setSourcePosition(expression);
block.getVariableScope().getReferencedLocalVariables().put("_outerInstance",outer);
VariableExpression thisObject = new VariableExpression("_thisObject");
thisObject.setSourcePosition(expression);
block.getVariableScope().getReferencedLocalVariables().put("_thisObject",thisObject);
TupleExpression conArgs = new TupleExpression();
conArgs.addExpression(outer);
conArgs.addExpression(thisObject);
block.addStatement(
new ExpressionStatement(
new ConstructorCallExpression(
ClassNode.SUPER,
conArgs)));
// lets 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(type);
paramField = answer.addField(paramName, ACC_PRIVATE, type, initialValue);
paramField.setHolder(true);
String methodName = Verifier.capitalize(paramName);
// lets 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;
}
protected Parameter[] getClosureSharedVariables(ClosureExpression ce){
VariableScope scope = ce.getVariableScope();
Map references = scope.getReferencedLocalVariables();
Parameter[] ret = new Parameter[references.size()];
int index = 0;
for (Iterator iter = references.values().iterator(); iter.hasNext();) {
org.codehaus.groovy.ast.Variable element = (org.codehaus.groovy.ast.Variable) iter.next();
if (element instanceof Parameter) {
ret[index] = (Parameter) element;
} else {
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 ClassNode createGStringClass(GStringExpression expression) {
ClassNode owner = classNode;
if (owner instanceof InnerClassNode) {
owner = owner.getOuterClass();
}
String outerClassName = owner.getName();
String name = outerClassName + "$" + context.getNextInnerClassIdx();
InnerClassNode answer = new InnerClassNode(owner, name, 0, ClassHelper.GSTRING_TYPE);
answer.setEnclosingMethod(this.methodNode);
FieldNode stringsField =
answer.addField(
"strings",
ACC_PRIVATE /*| ACC_STATIC*/,
ClassHelper.STRING_TYPE.makeArray(),
new ArrayExpression(ClassHelper.STRING_TYPE, expression.getStrings()));
answer.addMethod(
"getStrings",
ACC_PUBLIC,
ClassHelper.STRING_TYPE.makeArray(),
Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY,
new ReturnStatement(new FieldExpression(stringsField)));
// lets make the constructor
BlockStatement block = new BlockStatement();
block.addStatement(
new ExpressionStatement(
new ConstructorCallExpression(ClassNode.SUPER, new VariableExpression("values"))));
Parameter[] contructorParams = new Parameter[] { new Parameter(ClassHelper.OBJECT_TYPE.makeArray(), "values")};
answer.addConstructor(ACC_PUBLIC, contructorParams, ClassNode.EMPTY_ARRAY, block);
return answer;
}
protected void doConvertAndCast(ClassNode type){
doConvertAndCast(type,false);
}
protected void doConvertAndCast(ClassNode type, boolean coerce) {
if (type==ClassHelper.OBJECT_TYPE) return;
if (isValidTypeForCast(type)) {
visitClassExpression(new ClassExpression(type));
if (coerce) {
asTypeMethod.call(cv);
} else {
castToTypeMethod.call(cv);
}
}
helper.doCast(type);
}
protected void evaluateLogicalOrExpression(BinaryExpression expression) {
visitBooleanExpression(new BooleanExpression(expression.getLeftExpression()));
Label l0 = new Label();
Label l2 = new Label();
cv.visitJumpInsn(IFEQ, l0);
cv.visitLabel(l2);
visitConstantExpression(ConstantExpression.TRUE);
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
visitBooleanExpression(new BooleanExpression(expression.getRightExpression()));
cv.visitJumpInsn(IFNE, l2);
visitConstantExpression(ConstantExpression.FALSE);
cv.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();
cv.visitJumpInsn(IFEQ, l0);
visitBooleanExpression(new BooleanExpression(expression.getRightExpression()));
cv.visitJumpInsn(IFEQ, l0);
visitConstantExpression(ConstantExpression.TRUE);
Label l1 = new Label();
cv.visitJumpInsn(GOTO, l1);
cv.visitLabel(l0);
visitConstantExpression(ConstantExpression.FALSE);
cv.visitLabel(l1);
}
protected void evaluateBinaryExpression(String method, BinaryExpression expression) {
makeCall(
expression.getLeftExpression(),
new ConstantExpression(method),
new ArgumentListExpression().addExpression(expression.getRightExpression()),
invokeMethod, false, false, false
);
}
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(cv);
}
protected void evaluateBinaryExpressionWithAsignment(String method, BinaryExpression expression) {
Expression leftExpression = expression.getLeftExpression();
if (leftExpression instanceof BinaryExpression) {
BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
// lets 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)])
MethodCallExpression methodCall =
new MethodCallExpression(
expression.getLeftExpression(),
method,
new ArgumentListExpression(new Expression[] { expression.getRightExpression()}));
Expression safeIndexExpr = createReusableExpression(leftBinExpr.getRightExpression());
visitMethodCallExpression(
new MethodCallExpression(
leftBinExpr.getLeftExpression(),
"putAt",
new ArgumentListExpression(new Expression[] { safeIndexExpr, methodCall })));
//cv.visitInsn(POP);
return;
}
}
evaluateBinaryExpression(method, expression);
// br to leave a copy of rvalue on the stack. see also isPopRequired()
cv.visitInsn(DUP);
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(cv);
}
protected void evaluateEqual(BinaryExpression expression) {
Expression leftExpression = expression.getLeftExpression();
if (leftExpression instanceof BinaryExpression) {
BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
// lets replace this assignment to a subscript operator with a
// method call
// e.g. x[5] = 10
// -> (x, [], 5), =, 10
// -> methodCall(x, "putAt", [5, 10])
visitMethodCallExpression(
new MethodCallExpression(
leftBinExpr.getLeftExpression(),
"putAt",
new ArgumentListExpression(
new Expression[] { leftBinExpr.getRightExpression(), expression.getRightExpression()})));
// cv.visitInsn(POP); //this is realted to isPopRequired()
return;
}
}
// lets evaluate the RHS then hopefully the LHS will be a field
Expression rightExpression = expression.getRightExpression();
ClassNode type = getLHSType(leftExpression);
// lets not cast for primitive types as we handle these in field setting etc
if (ClassHelper.isPrimitiveType(type)) {
visitAndAutoboxBoolean(rightExpression);
} else if (type!=ClassHelper.OBJECT_TYPE){
visitCastExpression(new CastExpression(type, rightExpression));
} else {
visitAndAutoboxBoolean(rightExpression);
}
cv.visitInsn(DUP); // to leave a copy of the rightexpression value on the stack after the assignment.
leftHandExpression = true;
leftExpression.visit(this);
leftHandExpression = false;
}
/**
* 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.getField(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 ClassHelper.DYNAMIC_TYPE;
}
protected boolean isValidTypeForCast(ClassNode type) {
return type!=ClassHelper.DYNAMIC_TYPE &&
type!=ClassHelper.REFERENCE_TYPE;
}
protected void visitAndAutoboxBoolean(Expression expression) {
expression.visit(this);
if (isComparisonExpression(expression)) {
helper.boxBoolean(); // convert boolean to Boolean
}
}
protected void evaluatePrefixMethod(String method, Expression expression) {
// execute method
makeCall(
expression,
new ConstantExpression(method),
MethodCallExpression.NO_ARGUMENTS,invokeMethod,
false,false,false);
// store
leftHandExpression = true;
expression.visit(this);
// reload new value
leftHandExpression = false;
expression.visit(this);
}
protected void evaluatePostfixMethod(String method, Expression expression) {
// load
expression.visit(this);
// save value for later
int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, true);
//execute method
makeCall(
expression, new ConstantExpression(method),
MethodCallExpression.NO_ARGUMENTS,
invokeMethod,false,false, false);
// store
leftHandExpression = true;
expression.visit(this);
leftHandExpression = false;
//reload saved value
cv.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);
cv.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;
}
/**
* @return true if the given expression represents a non-static field
*/
protected boolean isNonStaticField(Expression expression) {
FieldNode field = null;
if (expression instanceof VariableExpression) {
VariableExpression varExp = (VariableExpression) expression;
field = classNode.getField(varExp.getName());
}
else if (expression instanceof FieldExpression) {
FieldExpression fieldExp = (FieldExpression) expression;
field = classNode.getField(fieldExp.getFieldName());
}
else if (expression.getClass()==PropertyExpression.class) {
PropertyExpression fieldExp = (PropertyExpression) expression;
String possibleField = fieldExp.getPropertyAsString();
if (possibleField!=null) field = classNode.getField(possibleField);
}
if (field != null) {
return !field.isStatic();
}
return false;
}
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) {
int line = statement.getLineNumber();
int col = statement.getColumnNumber();
this.currentASTNode = statement;
if (line >=0) {
lineNumber = line;
columnNumber = col;
}
if (CREATE_LINE_NUMBER_INFO && line >= 0 && cv != null) {
Label l = new Label();
cv.visitLabel(l);
cv.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.getField(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) {
if (expression == VariableExpression.THIS_EXPRESSION) {
return classNode;
}else if (expression==VariableExpression.SUPER_EXPRESSION) {
return classNode.getSuperClass();
}
VariableExpression varExpr = (VariableExpression) expression;
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().getReferencedClassVariables().get(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 isStaticMethod() {
if (methodNode == null) { // we're in a constructor
return false;
}
return methodNode.isStatic();
}
protected CompileUnit getCompileUnit() {
CompileUnit answer = classNode.getCompileUnit();
if (answer == null) {
answer = context.getCompileUnit();
}
return answer;
}
protected boolean isHolderVariable(Expression expression) {
if (expression instanceof FieldExpression) {
FieldExpression fieldExp = (FieldExpression) expression;
return fieldExp.getField().isHolder();
}
if (expression instanceof VariableExpression) {
VariableExpression varExp = (VariableExpression) expression;
Variable variable = compileStack.getVariable(varExp.getName(),false);
if (variable != null) {
return variable.isHolder();
}
FieldNode field = classNode.getField(varExp.getName());
if (field != null) {
return field.isHolder();
}
}
return false;
}
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;
}
}