| /* |
| * Copyright 2003-2009 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.codehaus.groovy.classgen.asm; |
| |
| import java.util.List; |
| |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.FieldNode; |
| 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.CastExpression; |
| import org.codehaus.groovy.ast.expr.ClassExpression; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.MethodCallExpression; |
| 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.classgen.AsmClassGenerator; |
| import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.StatementMeta; |
| import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; |
| import org.objectweb.asm.MethodVisitor; |
| |
| import static org.objectweb.asm.Opcodes.*; |
| |
| public class InvocationWriter { |
| // method invocation |
| private static final MethodCallerMultiAdapter invokeMethodOnCurrent = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnCurrent", true, false); |
| private static final MethodCallerMultiAdapter invokeMethodOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnSuper", true, false); |
| private static final MethodCallerMultiAdapter invokeMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethod", true, false); |
| private static final MethodCallerMultiAdapter invokeStaticMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeStaticMethod", true, true); |
| private static final MethodCaller invokeClosureMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "invokeClosure"); |
| |
| 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); |
| } |
| |
| private 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(); |
| |
| MethodVisitor mv = controller.getMethodVisitor(); |
| int opcode = INVOKEVIRTUAL; |
| if (target.isStatic()) { |
| opcode = INVOKESTATIC; |
| } else if (target.isPrivate()) { |
| opcode = INVOKESPECIAL; |
| } |
| |
| // handle receiver |
| int argumentsToRemove = 0; |
| if (opcode!=INVOKESTATIC) { |
| if (receiver!=null) { |
| // load receiver if not static invocation |
| compileStack.pushImplicitThis(implicitThis); |
| receiver.visit(controller.getAcg()); |
| operandStack.doGroovyCast(target.getDeclaringClass()); |
| compileStack.popImplicitThis(); |
| argumentsToRemove++; |
| } else { |
| mv.visitIntInsn(ALOAD,0); |
| } |
| } |
| |
| // load arguments |
| Parameter[] para = target.getParameters(); |
| List<Expression> argumentList = args.getExpressions(); |
| for (int i=0; i<argumentList.size(); i++) { |
| argumentList.get(i).visit(controller.getAcg()); |
| controller.getOperandStack().doGroovyCast(para[i].getType()); |
| } |
| |
| String owner = BytecodeHelper.getClassInternalName(target.getDeclaringClass()); |
| String desc = BytecodeHelper.getMethodDescriptor(target.getReturnType(), target.getParameters()); |
| mv.visitMethodInsn(opcode, owner, methodName, desc); |
| ClassNode ret = target.getReturnType().redirect(); |
| if (ret==ClassHelper.VOID_TYPE) { |
| ret = ClassHelper.OBJECT_TYPE; |
| mv.visitInsn(ACONST_NULL); |
| } |
| argumentsToRemove += args.getExpressions().size(); |
| controller.getOperandStack().replace(ret, argumentsToRemove); |
| return true; |
| } |
| |
| private void makeCall( |
| Expression origin, ClassExpression sender, |
| Expression receiver, Expression message, Expression arguments, |
| MethodCallerMultiAdapter adapter, |
| boolean safe, boolean spreadSafe, boolean implicitThis |
| ) { |
| // optimization path |
| boolean fittingAdapter = adapter == invokeMethodOnCurrent || |
| adapter == invokeStaticMethod; |
| if (fittingAdapter && controller.optimizeForInt && controller.isFastPath()) { |
| String methodName = getMethodName(message); |
| if (methodName != null) { |
| TupleExpression args; |
| if (arguments instanceof TupleExpression) { |
| args = (TupleExpression) arguments; |
| } else { |
| args = new TupleExpression(receiver); |
| } |
| |
| StatementMeta meta = null; |
| if (origin!=null) meta = (StatementMeta) origin.getNodeMetaData(StatementMeta.class); |
| MethodNode mn = null; |
| if (meta!=null) mn = meta.target; |
| |
| if (writeDirectMethodCall(mn, true, null, args)) return; |
| } |
| } |
| |
| boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments); |
| if (!containsSpreadExpression && origin instanceof MethodCallExpression) { |
| MethodCallExpression mce = (MethodCallExpression) origin; |
| MethodNode target = mce.getMethodTarget(); |
| if (writeDirectMethodCall(target, implicitThis, receiver, makeArgumentList(arguments))) return; |
| } |
| |
| // prepare call site |
| if ((adapter == invokeMethod || adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod) && !spreadSafe) { |
| String methodName = getMethodName(message); |
| |
| if (methodName != null) { |
| controller.getCallSiteWriter().makeCallSite( |
| receiver, methodName, arguments, safe, implicitThis, |
| adapter == invokeMethodOnCurrent, |
| adapter == invokeStaticMethod); |
| return; |
| } |
| } |
| |
| OperandStack operandStack = controller.getOperandStack(); |
| CompileStack compileStack = controller.getCompileStack(); |
| AsmClassGenerator acg = controller.getAcg(); |
| |
| // ensure VariableArguments are read, not stored |
| compileStack.pushLHS(false); |
| |
| // sender only for call sites |
| if (adapter == AsmClassGenerator.setProperty) { |
| ConstantExpression.NULL.visit(acg); |
| } else { |
| sender.visit(acg); |
| } |
| |
| // receiver |
| compileStack.pushImplicitThis(implicitThis); |
| receiver.visit(acg); |
| operandStack.box(); |
| compileStack.popImplicitThis(); |
| |
| |
| int operandsToRemove = 2; |
| // message |
| if (message != null) { |
| message.visit(acg); |
| operandStack.box(); |
| operandsToRemove++; |
| } |
| |
| // arguments |
| int numberOfArguments = containsSpreadExpression ? -1 : AsmClassGenerator.argumentSize(arguments); |
| if (numberOfArguments > 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); |
| } |
| } |
| |
| adapter.call(controller.getMethodVisitor(), numberOfArguments, safe, spreadSafe); |
| |
| compileStack.popLHS(); |
| operandStack.replace(ClassHelper.OBJECT_TYPE,operandsToRemove); |
| } |
| |
| private 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; |
| } |
| |
| private String getMethodName(Expression message) { |
| String methodName = null; |
| if (message instanceof CastExpression) { |
| CastExpression msg = (CastExpression) message; |
| if (msg.getType() == ClassHelper.STRING_TYPE) { |
| final Expression methodExpr = msg.getExpression(); |
| if (methodExpr instanceof ConstantExpression) |
| methodName = methodExpr.getText(); |
| } |
| } |
| |
| if (methodName == null && message instanceof ConstantExpression) { |
| ConstantExpression constantExpression = (ConstantExpression) message; |
| methodName = constantExpression.getText(); |
| } |
| return methodName; |
| } |
| |
| 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 (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)); |
| 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); |
| } |
| } |