org.codehaus.groovy.classgen.asm.InvocationWriter Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.classgen.asm;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
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.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.StatementMeta;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling;
import org.codehaus.groovy.syntax.SyntaxException;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.DUP2_X1;
import static org.objectweb.asm.Opcodes.DUP_X1;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.SWAP;
public class InvocationWriter {
// method invocation
public static final MethodCallerMultiAdapter invokeMethodOnCurrent = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnCurrent", true, false);
public static final MethodCallerMultiAdapter invokeMethodOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnSuper", true, false);
public static final MethodCallerMultiAdapter invokeMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethod", true, false);
public static final MethodCallerMultiAdapter invokeStaticMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeStaticMethod", true, true);
public static final MethodCaller invokeClosureMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "invokeClosure");
public static final MethodCaller castToVargsArray = MethodCaller.newStatic(DefaultTypeTransformation.class, "castToVargsArray");
private static final MethodNode CLASS_FOR_NAME_STRING = ClassHelper.CLASS_Type.getDeclaredMethod("forName", new Parameter[]{new Parameter(ClassHelper.STRING_TYPE,"name")});
// type conversions
private static final MethodCaller
asTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "asType"),
castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType"),
castToClassMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToClass"),
castToStringMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToString"),
castToEnumMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToEnum");
// constructor calls with this() and super()
static final MethodCaller selectConstructorAndTransformArguments = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "selectConstructorAndTransformArguments");
private WriterController controller;
public InvocationWriter(WriterController wc) {
this.controller = wc;
}
private void makeInvokeMethodCall(MethodCallExpression call, boolean useSuper, MethodCallerMultiAdapter adapter) {
// receiver
// we operate on GroovyObject if possible
Expression objectExpression = call.getObjectExpression();
// message name
Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod());
if (useSuper) {
ClassNode classNode = controller.isInClosure() ? controller.getOutermostClass() : controller.getClassNode(); // GROOVY-4035
ClassNode superClass = classNode.getSuperClass();
makeCall(call, new ClassExpression(superClass),
objectExpression, messageName,
call.getArguments(), adapter,
call.isSafe(), call.isSpreadSafe(),
false
);
} else {
makeCall(call, objectExpression, messageName,
call.getArguments(), adapter,
call.isSafe(), call.isSpreadSafe(),
call.isImplicitThis()
);
}
}
public void makeCall(
Expression origin,
Expression receiver, Expression message, Expression arguments,
MethodCallerMultiAdapter adapter,
boolean safe, boolean spreadSafe, boolean implicitThis
) {
ClassNode cn = controller.getClassNode();
if (controller.isInClosure() && !implicitThis && AsmClassGenerator.isThisExpression(receiver)) cn=cn.getOuterClass();
makeCall(origin, new ClassExpression(cn), receiver, message, arguments,
adapter, safe, spreadSafe, implicitThis);
}
protected boolean writeDirectMethodCall(MethodNode target, boolean implicitThis, Expression receiver, TupleExpression args) {
if (target==null) return false;
String methodName = target.getName();
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
ClassNode declaringClass = target.getDeclaringClass();
ClassNode classNode = controller.getClassNode();
MethodVisitor mv = controller.getMethodVisitor();
int opcode = INVOKEVIRTUAL;
if (target.isStatic()) {
opcode = INVOKESTATIC;
} else if (target.isPrivate() || ((receiver instanceof VariableExpression && ((VariableExpression) receiver).isSuperExpression()))) {
opcode = INVOKESPECIAL;
} else if (declaringClass.isInterface()) {
opcode = INVOKEINTERFACE;
}
// handle receiver
int argumentsToRemove = 0;
if (opcode!=INVOKESTATIC) {
if (receiver!=null) {
// load receiver if not static invocation
// todo: fix inner class case
if (implicitThis
&& !classNode.isDerivedFrom(declaringClass)
&& !classNode.implementsInterface(declaringClass)
&& classNode instanceof InnerClassNode) {
// we are calling an outer class method
compileStack.pushImplicitThis(false);
if (controller.isInClosure()) {
new VariableExpression("thisObject").visit(controller.getAcg());
} else {
Expression expr = new PropertyExpression(new ClassExpression(declaringClass), "this");
expr.visit(controller.getAcg());
}
} else {
compileStack.pushImplicitThis(implicitThis);
receiver.visit(controller.getAcg());
}
operandStack.doGroovyCast(declaringClass);
compileStack.popImplicitThis();
argumentsToRemove++;
} else {
mv.visitIntInsn(ALOAD,0);
operandStack.push(classNode);
argumentsToRemove++;
}
}
int stackSize = operandStack.getStackLength();
String owner = BytecodeHelper.getClassInternalName(declaringClass);
ClassNode receiverType = receiver!=null?controller.getTypeChooser().resolveType(receiver, classNode):declaringClass;
if (opcode == INVOKEVIRTUAL && ClassHelper.OBJECT_TYPE.equals(declaringClass)) {
// avoid using a narrowed type if the method is defined on object because it can interfere
// with delegate type inference in static compilation mode and trigger a ClassCastException
receiverType = declaringClass;
}
if (opcode == INVOKEVIRTUAL) {
if (!receiverType.equals(declaringClass)
&& !ClassHelper.OBJECT_TYPE.equals(declaringClass)
&& !receiverType.isArray()
&& !receiverType.isInterface()
&& !ClassHelper.isPrimitiveType(receiverType) // e.g int.getClass()
&& receiverType.isDerivedFrom(declaringClass)) {
owner = BytecodeHelper.getClassInternalName(receiverType);
ClassNode top = operandStack.getTopOperand();
if (!receiverType.equals(top)) {
mv.visitTypeInsn(CHECKCAST, owner);
}
} else if (target.isPublic()
&& (!Modifier.isPublic(declaringClass.getModifiers())
&& !receiverType.equals(declaringClass))
&& receiverType.isDerivedFrom(declaringClass)
&& !receiverType.getPackageName().equals(classNode.getPackageName())) {
// package private class, public method
// see GROOVY-6962
owner = BytecodeHelper.getClassInternalName(receiverType);
}
}
loadArguments(args.getExpressions(), target.getParameters());
String desc = BytecodeHelper.getMethodDescriptor(target.getReturnType(), target.getParameters());
mv.visitMethodInsn(opcode, owner, methodName, desc, declaringClass.isInterface());
ClassNode ret = target.getReturnType().redirect();
if (ret==ClassHelper.VOID_TYPE) {
ret = ClassHelper.OBJECT_TYPE;
mv.visitInsn(ACONST_NULL);
}
argumentsToRemove += (operandStack.getStackLength()-stackSize);
controller.getOperandStack().remove(argumentsToRemove);
controller.getOperandStack().push(ret);
return true;
}
private boolean lastIsArray(List argumentList, int pos) {
Expression last = argumentList.get(pos);
ClassNode type = controller.getTypeChooser().resolveType(last, controller.getClassNode());
return type.isArray();
}
// load arguments
protected void loadArguments(List argumentList, Parameter[] para) {
if (para.length==0) return;
ClassNode lastParaType = para[para.length - 1].getOriginType();
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
if (lastParaType.isArray()
&& (argumentList.size()>para.length || argumentList.size()==para.length-1 || !lastIsArray(argumentList, para.length-1))) {
int stackLen = operandStack.getStackLength()+argumentList.size();
MethodVisitor mv = controller.getMethodVisitor();
//mv = new org.objectweb.asm.util.TraceMethodVisitor(mv);
controller.setMethodVisitor(mv);
// varg call
// first parameters as usual
for (int i = 0; i < para.length-1; i++) {
argumentList.get(i).visit(acg);
operandStack.doGroovyCast(para[i].getType());
}
// last parameters wrapped in an array
List lastParams = new LinkedList();
for (int i=para.length-1; i MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) {
ArgumentListExpression ae = makeArgumentList(arguments);
if (containsSpreadExpression) {
acg.despreadList(ae.getExpressions(), true);
} else {
ae.visit(acg);
}
} else if (numberOfArguments > 0) {
operandsToRemove += numberOfArguments;
TupleExpression te = (TupleExpression) arguments;
for (int i = 0; i < numberOfArguments; i++) {
Expression argument = te.getExpression(i);
argument.visit(acg);
operandStack.box();
if (argument instanceof CastExpression) acg.loadWrapper(argument);
}
}
if (adapter==null) adapter = invokeMethod;
adapter.call(controller.getMethodVisitor(), numberOfArguments, safe, spreadSafe);
compileStack.popLHS();
operandStack.replace(ClassHelper.OBJECT_TYPE,operandsToRemove);
}
protected void makeCall(
Expression origin, ClassExpression sender,
Expression receiver, Expression message, Expression arguments,
MethodCallerMultiAdapter adapter,
boolean safe, boolean spreadSafe, boolean implicitThis
) {
// direct method call paths
boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments);
if (makeDirectCall(origin, receiver, message, arguments, adapter, implicitThis, containsSpreadExpression)) return;
// normal path
if (makeCachedCall(origin, sender, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis, containsSpreadExpression)) return;
// path through ScriptBytecodeAdapter
makeUncachedCall(origin, sender, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis, containsSpreadExpression);
}
/**
* if Class.forName(x) is recognized, make a direct method call
*/
protected boolean makeClassForNameCall(Expression origin, Expression receiver, Expression message, Expression arguments) {
if (! (receiver instanceof ClassExpression)) return false;
ClassExpression ce = (ClassExpression) receiver;
if (!ClassHelper.CLASS_Type.equals(ce.getType())) return false;
String msg = getMethodName(message);
if (!"forName".equals(msg)) return false;
ArgumentListExpression ae = makeArgumentList(arguments);
if (ae.getExpressions().size()!=1) return false;
return writeDirectMethodCall(CLASS_FOR_NAME_STRING,false, receiver, ae);
}
public static ArgumentListExpression makeArgumentList(Expression arguments) {
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);
}
return ae;
}
protected String getMethodName(Expression message) {
String methodName = null;
if (message instanceof CastExpression) {
CastExpression msg = (CastExpression) message;
if (msg.getType() == ClassHelper.STRING_TYPE) {
final Expression methodExpr = msg.getExpression();
if (methodExpr instanceof ConstantExpression)
methodName = methodExpr.getText();
}
}
if (methodName == null && message instanceof ConstantExpression) {
ConstantExpression constantExpression = (ConstantExpression) message;
methodName = constantExpression.getText();
}
return methodName;
}
public void writeInvokeMethod(MethodCallExpression call) {
if (isClosureCall(call)) {
// let's invoke the closure method
invokeClosure(call.getArguments(), call.getMethodAsString());
} else {
boolean isSuperMethodCall = usesSuper(call);
MethodCallerMultiAdapter adapter = invokeMethod;
if (isSuperMethodCall && call.isSafe()) {
// safe is not necessary here because "super" is always not null
// but keeping the flag would trigger a VerifyError (see GROOVY-6045)
call.setSafe(false);
}
if (AsmClassGenerator.isThisExpression(call.getObjectExpression())) adapter = invokeMethodOnCurrent;
if (isSuperMethodCall) adapter = invokeMethodOnSuper;
if (isStaticInvocation(call)) adapter = invokeStaticMethod;
makeInvokeMethodCall(call, isSuperMethodCall, adapter);
}
}
private boolean isClosureCall(MethodCallExpression call) {
// are we a local variable?
// it should not be an explicitly "this" qualified method call
// and the current class should have a possible method
ClassNode classNode = controller.getClassNode();
String methodName = call.getMethodAsString();
if (methodName==null) return false;
if (!call.isImplicitThis()) return false;
if (!AsmClassGenerator.isThisExpression(call.getObjectExpression())) return false;
FieldNode field = classNode.getDeclaredField(methodName);
if (field == null) return false;
if (isStaticInvocation(call) && !field.isStatic()) return false;
Expression arguments = call.getArguments();
return ! classNode.hasPossibleMethod(methodName, arguments);
}
private void invokeClosure(Expression arguments, String methodName) {
AsmClassGenerator acg = controller.getAcg();
acg.visitVariableExpression(new VariableExpression(methodName));
controller.getOperandStack().box();
if (arguments instanceof TupleExpression) {
arguments.visit(acg);
} else {
new TupleExpression(arguments).visit(acg);
}
invokeClosureMethod.call(controller.getMethodVisitor());
controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE);
}
private boolean isStaticInvocation(MethodCallExpression call) {
if (!AsmClassGenerator.isThisExpression(call.getObjectExpression())) return false;
if (controller.isStaticMethod()) return true;
return controller.isStaticContext() && !call.isImplicitThis();
}
private 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 void writeInvokeStaticMethod(StaticMethodCallExpression call) {
makeCall(call,
new ClassExpression(call.getOwnerType()),
new ConstantExpression(call.getMethod()),
call.getArguments(),
InvocationWriter.invokeStaticMethod,
false, false, false);
}
private boolean writeDirectConstructorCall(ConstructorCallExpression call) {
if (!controller.isFastPath()) return false;
StatementMeta meta = call.getNodeMetaData(StatementMeta.class);
ConstructorNode cn = null;
if (meta!=null) cn = (ConstructorNode) meta.target;
if (cn==null) return false;
String ownerDescriptor = prepareConstructorCall(cn);
TupleExpression args = makeArgumentList(call.getArguments());
loadArguments(args.getExpressions(), cn.getParameters());
finnishConstructorCall(cn, ownerDescriptor, args.getExpressions().size());
return true;
}
protected String prepareConstructorCall(ConstructorNode cn) {
String owner = BytecodeHelper.getClassInternalName(cn.getDeclaringClass());
MethodVisitor mv = controller.getMethodVisitor();
mv.visitTypeInsn(NEW, owner);
mv.visitInsn(DUP);
return owner;
}
protected void finnishConstructorCall(ConstructorNode cn, String ownerDescriptor, int argsToRemove) {
String desc = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, cn.getParameters());
MethodVisitor mv = controller.getMethodVisitor();
mv.visitMethodInsn(INVOKESPECIAL, ownerDescriptor, "", desc, false);
controller.getOperandStack().remove(argsToRemove);
controller.getOperandStack().push(cn.getDeclaringClass());
}
protected void writeNormalConstructorCall(ConstructorCallExpression call) {
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());
controller.getCallSiteWriter().makeCallSite(
receiverClass, CallSiteWriter.CONSTRUCTOR,
arguments, false, false, false,
false);
}
public void writeInvokeConstructor(ConstructorCallExpression call) {
if (writeDirectConstructorCall(call)) return;
if (writeAICCall(call)) return;
writeNormalConstructorCall(call);
}
protected boolean writeAICCall(ConstructorCallExpression call) {
if (!call.isUsingAnonymousInnerClass()) return false;
ConstructorNode cn = call.getType().getDeclaredConstructors().get(0);
OperandStack os = controller.getOperandStack();
String ownerDescriptor = prepareConstructorCall(cn);
List args = makeArgumentList(call.getArguments()).getExpressions();
Parameter[] params = cn.getParameters();
// if a this appears as parameter here, then it should be
// not static, unless we are in a static method. But since
// ACG#visitVariableExpression does the opposite for this case, we
// push here an explicit this. This should not have any negative effect
// sine visiting a method call or property with implicit this will push
// a new value for this again.
controller.getCompileStack().pushImplicitThis(true);
for (int i=0; i constructors = sortConstructors(call, callNode);
if (!makeDirectConstructorCall(constructors, call, callNode)) {
makeMOPBasedConstructorCall(constructors, call, callNode);
}
}
private static 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 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c0.getParameters());
String descriptor1 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c1.getParameters());
return descriptor0.compareTo(descriptor1);
}
};
Collections.sort(constructors, comp);
return constructors;
}
private boolean makeDirectConstructorCall(List constructors, ConstructorCallExpression call, ClassNode callNode) {
if (!controller.isConstructor()) return false;
Expression arguments = call.getArguments();
List argumentList;
if (arguments instanceof TupleExpression) {
argumentList = ((TupleExpression) arguments).getExpressions();
} else {
argumentList = new ArrayList();
argumentList.add(arguments);
}
for (Expression expression : argumentList) {
if (expression instanceof SpreadExpression) return false;
}
ConstructorNode cn = getMatchingConstructor(constructors, argumentList);
if (cn==null) return false;
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
Parameter[] params = cn.getParameters();
mv.visitVarInsn(ALOAD, 0);
for (int i=0; i", descriptor, false);
return true;
}
private void makeMOPBasedConstructorCall(List constructors, ConstructorCallExpression call, ClassNode callNode) {
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
call.getArguments().visit(controller.getAcg());
// keep Object[] on stack
mv.visitInsn(DUP);
// to select the constructor we need also the number of
// available constructors and the class we want to make
// the call on
BytecodeHelper.pushConstant(mv, -1);
controller.getAcg().visitClassExpression(new ClassExpression(callNode));
operandStack.remove(1);
// removes one Object[] leaves the int containing the
// call flags and the constructor number
selectConstructorAndTransformArguments.call(mv);
//load "this"
if (controller.isConstructor()) {
mv.visitVarInsn(ALOAD, 0);
} else {
mv.visitTypeInsn(NEW, BytecodeHelper.getClassInternalName(callNode));
}
mv.visitInsn(SWAP);
TreeMap sortedConstructors = new TreeMap();
for (ConstructorNode constructor : constructors) {
String typeDescriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, constructor.getParameters());
int hash = BytecodeHelper.hashCode(typeDescriptor);
ConstructorNode sameHashNode = sortedConstructors.put(hash, constructor);
if (sameHashNode!=null) {
controller.getSourceUnit().addError(
new SyntaxException("Unable to compile class "+controller.getClassNode().getName() + " due to hash collision in constructors", call.getLineNumber(), call.getColumnNumber()));
}
}
Label[] targets = new Label[constructors.size()];
int[] indices = new int[constructors.size()];
Iterator hashIt = sortedConstructors.keySet().iterator();
Iterator constructorIt = sortedConstructors.values().iterator();
for (int i = 0; i < targets.length; i++) {
targets[i] = new Label();
indices[i] = hashIt.next();
}
// create switch targets
Label defaultLabel = new Label();
Label afterSwitch = new Label();
mv.visitLookupSwitchInsn(defaultLabel, indices, targets);
for (int i = 0; i < targets.length; i++) {
mv.visitLabel(targets[i]);
// to keep the stack height, we need to leave
// one Object[] on the stack as last element. At the
// same time, we need the Object[] on top of the stack
// to extract the parameters.
if (controller.isConstructor()) {
// in this case we need one "this", so a SWAP will exchange
// "this" and Object[], a DUP_X1 will then copy the Object[]
/// to the last place in the stack:
// Object[],this -SWAP-> this,Object[]
// this,Object[] -DUP_X1-> Object[],this,Object[]
mv.visitInsn(SWAP);
mv.visitInsn(DUP_X1);
} else {
// in this case we need two "this" in between and the Object[]
// at the bottom of the stack as well as on top for our invokeSpecial
// So we do DUP_X1, DUP2_X1, POP
// Object[],this -DUP_X1-> this,Object[],this
// this,Object[],this -DUP2_X1-> Object[],this,this,Object[],this
// Object[],this,this,Object[],this -POP-> Object[],this,this,Object[]
mv.visitInsn(DUP_X1);
mv.visitInsn(DUP2_X1);
mv.visitInsn(POP);
}
ConstructorNode cn = constructorIt.next();
String descriptor = BytecodeHelper.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
// vargs need special attention and transformation though
Parameter[] parameters = cn.getParameters();
int lengthWithoutVargs = parameters.length;
if (parameters.length>0 && parameters[parameters.length-1].getType().isArray()) {
lengthWithoutVargs--;
}
for (int p = 0; p < lengthWithoutVargs; p++) {
loadAndCastElement(operandStack, mv, parameters, p);
}
if (parameters.length>lengthWithoutVargs) {
ClassNode type = parameters[lengthWithoutVargs].getType();
BytecodeHelper.pushConstant(mv, lengthWithoutVargs);
controller.getAcg().visitClassExpression(new ClassExpression(type));
operandStack.remove(1);
castToVargsArray.call(mv);
BytecodeHelper.doCast(mv, type);
} else {
// at the end we remove the Object[]
// the vargs case simply the last swap so no pop is needed
mv.visitInsn(POP);
}
// make the constructor call
mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "", descriptor, false);
mv.visitJumpInsn(GOTO, afterSwitch);
}
mv.visitLabel(defaultLabel);
// this part should never be reached!
mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
mv.visitInsn(DUP);
mv.visitLdcInsn("This class has been compiled with a super class which is binary incompatible with the current super class found on classpath. You should recompile this class with the new version.");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V", false);
mv.visitInsn(ATHROW);
mv.visitLabel(afterSwitch);
// For a special constructor call inside a constructor we don't need
// any result object on the stack, for outside the constructor we do.
// to keep the stack height for the able we kept one object as dummy
// result on the stack, which we can remove now if inside a constructor.
if (!controller.isConstructor()) {
// in case we are not in a constructor we have an additional
// object on the stack, the result of our constructor call
// which we want to keep, so we swap with the dummy object and
// do normal removal of it. In the end, the call result will be
// on the stack then
mv.visitInsn(SWAP);
operandStack.push(callNode); // for call result
}
mv.visitInsn(POP);
}
private static void loadAndCastElement(OperandStack operandStack, MethodVisitor mv, Parameter[] parameters, int p) {
operandStack.push(ClassHelper.OBJECT_TYPE);
mv.visitInsn(DUP);
BytecodeHelper.pushConstant(mv, p);
mv.visitInsn(AALOAD);
operandStack.push(ClassHelper.OBJECT_TYPE);
ClassNode type = parameters[p].getType();
operandStack.doGroovyCast(type);
operandStack.swap();
operandStack.remove(2);
}
// we match only on the number of arguments, not anything else
private static ConstructorNode getMatchingConstructor(List constructors, List argumentList) {
ConstructorNode lastMatch = null;
for (int i=0; i
© 2015 - 2024 Weber Informatics LLC | Privacy Policy