| /* |
| * 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.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.runtime.ScriptBytecodeAdapter; |
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; |
| import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling; |
| import org.codehaus.groovy.syntax.SyntaxException; |
| import org.codehaus.groovy.transform.stc.ExtensionMethodNode; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.TreeMap; |
| |
| import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant; |
| import static org.apache.groovy.ast.tools.ExpressionUtils.isSuperExpression; |
| import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression; |
| import static org.codehaus.groovy.ast.ClassHelper.isClassType; |
| import static org.codehaus.groovy.ast.ClassHelper.isFunctionalInterface; |
| import static org.codehaus.groovy.ast.ClassHelper.isObjectType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid; |
| import static org.codehaus.groovy.ast.ClassHelper.isStringType; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.isNullOrInstanceOfX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.ternaryX; |
| import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs; |
| import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; |
| import static org.objectweb.asm.Opcodes.AALOAD; |
| import static org.objectweb.asm.Opcodes.ACC_FINAL; |
| import static org.objectweb.asm.Opcodes.ACC_PUBLIC; |
| 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"); |
| private static final MethodCaller castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType"); |
| private static final MethodCaller castToClassMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToClass"); |
| private static final MethodCaller castToStringMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToString"); |
| private static final MethodCaller castToEnumMethod = MethodCaller.newStatic(ShortTypeHandling.class, "castToEnum"); |
| |
| // constructor calls with this() and super() |
| private static final MethodCaller selectConstructorAndTransformArguments = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "selectConstructorAndTransformArguments"); |
| |
| protected final WriterController controller; |
| |
| public InvocationWriter(final WriterController controller) { |
| this.controller = controller; |
| } |
| |
| public void makeCall(final Expression origin, final Expression receiver, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, boolean safe, final boolean spreadSafe, boolean implicitThis) { |
| ClassNode sender; |
| if (isSuperExpression(receiver) || (isThisExpression(receiver) && !implicitThis)) { |
| // GROOVY-6045, GROOVY-8693, et al. |
| sender = controller.getThisType(); |
| implicitThis = false; |
| safe = false; |
| } else { |
| sender = controller.getClassNode(); |
| } |
| makeCall(origin, new ClassExpression(sender), receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); |
| } |
| |
| protected void makeCall(final Expression origin, final ClassExpression sender, final Expression receiver, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, final boolean safe, final boolean spreadSafe, final boolean implicitThis) { |
| boolean containsSpreadExpression = AsmClassGenerator.containsSpreadExpression(arguments); |
| // direct invocation path |
| if (!makeDirectCall(origin, receiver, message, arguments, adapter, implicitThis, containsSpreadExpression)) |
| // call site or indy path |
| if (!makeCachedCall(origin, sender, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis, containsSpreadExpression)) |
| // ScriptBytecodeAdapter path |
| makeUncachedCall(origin, sender, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis, containsSpreadExpression); |
| } |
| |
| protected boolean writeDirectMethodCall(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args) { |
| if (target == null || target instanceof ExtensionMethodNode) return false; |
| |
| ClassNode declaringClass = target.getDeclaringClass(); |
| ClassNode enclosingClass = controller.getClassNode(), receiverType = enclosingClass; |
| if (receiver != null) { |
| receiverType = controller.getTypeChooser().resolveType(receiver, enclosingClass); |
| if (target.isStatic() && isClassClassNodeWrappingConcreteType(receiverType)) { |
| receiverType = receiverType.getGenericsTypes()[0].getType(); |
| } |
| } |
| |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| int startDepth = operandStack.getStackLength(); |
| |
| // handle receiver |
| if (!target.isStatic()) { |
| if (receiver != null) { |
| Expression objectExpression = receiver; |
| if (!implicitThis && callSuperDefault(enclosingClass, target, receiver)) { |
| compileStack.pushImplicitThis(true); |
| objectExpression = new VariableExpression("this", declaringClass); |
| } else if (implicitThis |
| && enclosingClass.getOuterClass() != null |
| && !enclosingClass.isDerivedFrom(declaringClass) |
| && !enclosingClass.implementsInterface(declaringClass)) { |
| // outer class method invocation |
| compileStack.pushImplicitThis(false); |
| if (!controller.isInGeneratedFunction() && isThis(receiver)) { |
| objectExpression = new PropertyExpression(new ClassExpression(declaringClass), "this"); |
| } |
| } else { |
| compileStack.pushImplicitThis(implicitThis); |
| } |
| objectExpression.visit(controller.getAcg()); |
| operandStack.doGroovyCast(declaringClass); |
| compileStack.popImplicitThis(); |
| } else { |
| mv.visitIntInsn(ALOAD, 0); |
| operandStack.push(enclosingClass); |
| } |
| } |
| |
| int opcode; |
| if (target.isStatic()) { |
| opcode = INVOKESTATIC; |
| } else if (isSuperExpression(receiver) || isClassWithSuper(receiver)) { |
| opcode = INVOKESPECIAL; |
| } else if (declaringClass.isInterface()) { |
| opcode = INVOKEINTERFACE; |
| } else { |
| opcode = INVOKEVIRTUAL; |
| } |
| |
| ClassNode ownerClass = declaringClass; |
| if (opcode == INVOKESPECIAL) { // GROOVY-8693, GROOVY-9909 |
| if (!declaringClass.isInterface() || receiverType.implementsInterface(declaringClass)) ownerClass = receiverType; |
| } else if (opcode == INVOKEVIRTUAL && isObjectType(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 |
| } else if (opcode == INVOKEVIRTUAL |
| && !receiverType.isArray() |
| && !receiverType.isInterface() |
| && !isPrimitiveType(receiverType) |
| && !receiverType.equals(declaringClass) |
| && receiverType.isDerivedFrom(declaringClass)) { |
| ownerClass = receiverType; // use actual for typical call |
| if (!receiverType.equals(operandStack.getTopOperand())) { |
| mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(ownerClass)); |
| } |
| } else if ((declaringClass.getModifiers() & (ACC_FINAL | ACC_PUBLIC)) == 0 && !receiverType.equals(declaringClass) |
| && (declaringClass.isInterface() ? receiverType.implementsInterface(declaringClass) : receiverType.isDerivedFrom(declaringClass))) { |
| // GROOVY-6962, GROOVY-9955, GROOVY-10380: method declared by inaccessible class or interface |
| if (declaringClass.isInterface() && !receiverType.isInterface()) opcode = INVOKEVIRTUAL; |
| ownerClass = receiverType; |
| } |
| |
| ClassNode returnType = target.getReturnType(); |
| Parameter[] parameters = target.getParameters(); |
| if (parameters.length > 0 && parameters[0].isReceiver()) |
| parameters = Arrays.copyOfRange(parameters, 1, parameters.length); |
| |
| loadArguments(args.getExpressions(), parameters); |
| |
| String ownerName = BytecodeHelper.getClassInternalName(ownerClass); |
| String signature = BytecodeHelper.getMethodDescriptor(returnType, parameters); |
| mv.visitMethodInsn(opcode, ownerName, target.getName(), signature, ownerClass.isInterface()); |
| |
| if (isPrimitiveVoid(returnType)) { |
| returnType = ClassHelper.OBJECT_TYPE; |
| mv.visitInsn(ACONST_NULL); |
| } |
| // replace the method call's receiver and argument types with the return type |
| operandStack.replace(returnType, operandStack.getStackLength() - startDepth); |
| return true; |
| } |
| |
| private boolean callSuperDefault(ClassNode enclosingClass, MethodNode target, Expression receiver) { |
| ClassNode declaringClass = target.getDeclaringClass(); |
| if (declaringClass.isInterface() && enclosingClass.implementsInterface(declaringClass)) { |
| return isClassWithSuper(receiver); |
| } |
| return false; |
| } |
| |
| private boolean isClassWithSuper(Expression exp) { |
| if (exp instanceof PropertyExpression) { |
| PropertyExpression pexp = (PropertyExpression) exp; |
| return pexp.getObjectExpression() instanceof ClassExpression && "super".equals(pexp.getPropertyAsString()); |
| } |
| return false; |
| } |
| |
| /** |
| * Supplements {@link org.apache.groovy.ast.tools.ExpressionUtils#isThisExpression isThisExpression} |
| * with the ability to see into {@code CheckcastReceiverExpression}. |
| */ |
| private static boolean isThis(final Expression expression) { |
| boolean[] isThis = new boolean[1]; |
| expression.visit(new org.codehaus.groovy.ast.GroovyCodeVisitorAdapter() { |
| @Override |
| public void visitVariableExpression(final VariableExpression vexp) { |
| isThis[0] = vexp.isThisExpression(); |
| } |
| }); |
| return isThis[0]; |
| } |
| |
| private boolean isArray(final Expression expression) { |
| if (isNullConstant(expression)) return true; // null is an array argument for variadic parameter |
| ClassNode type = controller.getTypeChooser().resolveType(expression, controller.getClassNode()); |
| return type.isArray(); |
| } |
| |
| protected void loadArguments(final List<Expression> arguments, final Parameter[] parameters) { |
| if (parameters.length == 0) return; |
| int nthParameter = parameters.length - 1; |
| ClassNode lastType = parameters[nthParameter].getOriginType(); |
| |
| OperandStack operandStack = controller.getOperandStack(); |
| int expected = operandStack.getStackLength() + arguments.size(); |
| boolean varg = lastType.isArray() && ( |
| arguments.size() > parameters.length |
| || arguments.size() == parameters.length - 1 |
| || !arguments.isEmpty() && !isArray(last(arguments))); |
| |
| for (int i = 0, n = varg ? nthParameter : arguments.size(); i < n; i += 1) { |
| Expression argument = arguments.get(i); |
| argument.visit(controller.getAcg()); |
| if (!isNullConstant(argument)) { |
| operandStack.doGroovyCast(parameters[i].getType()); |
| } |
| } |
| |
| if (varg) { |
| // last arguments wrapped in an array |
| List<Expression> lastArgs = arguments.subList(nthParameter, arguments.size()); |
| Expression array = new ArrayExpression(lastType.getComponentType(), lastArgs); |
| if (lastArgs.size() == 1) { // GROOVY-10722: disambiguate array and null cases |
| Expression lastExpr = lastArgs.get(0); // TODO: cache non-trivial expression value |
| array = ternaryX(isNullOrInstanceOfX(lastExpr, lastType), castX(lastType, lastExpr), array); |
| } |
| array.visit(controller.getAcg()); |
| |
| // adjust stack length |
| while (operandStack.getStackLength() < expected) { |
| operandStack.push(ClassHelper.OBJECT_TYPE); |
| } |
| if (arguments.size() == nthParameter) { |
| operandStack.remove(1); |
| } |
| } |
| } |
| |
| protected boolean makeDirectCall(Expression origin, Expression receiver, Expression message, Expression arguments, MethodCallerMultiAdapter adapter, boolean implicitThis, boolean containsSpreadExpression) { |
| if (makeClassForNameCall(origin, receiver, message, arguments)) return true; |
| if (controller.optimizeForInt && controller.isFastPath() // optimization path |
| && adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod) { |
| String methodName = getMethodName(message); |
| if (methodName != null) { |
| OptimizingStatementWriter.StatementMeta meta = origin.getNodeMetaData(OptimizingStatementWriter.StatementMeta.class); |
| if (meta != null && meta.target != null) { |
| TupleExpression args; |
| if (arguments instanceof TupleExpression) { |
| args = (TupleExpression) arguments; |
| } else { |
| args = new TupleExpression(receiver); |
| } |
| if (writeDirectMethodCall(meta.target, true, null, args)) return true; |
| } |
| } |
| } |
| if (containsSpreadExpression) return false; |
| if (origin instanceof MethodCallExpression) { |
| MethodCallExpression mce = (MethodCallExpression) origin; |
| if (mce.getMethodTarget() != null) |
| return writeDirectMethodCall(mce.getMethodTarget(), implicitThis, receiver, makeArgumentList(arguments)); |
| } |
| return false; |
| } |
| |
| protected boolean makeCachedCall(Expression origin, ClassExpression sender, Expression receiver, Expression message, Expression arguments, MethodCallerMultiAdapter adapter, boolean safe, boolean spreadSafe, boolean implicitThis, boolean containsSpreadExpression) { |
| // 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 true; |
| } |
| } |
| return false; |
| } |
| |
| protected void makeUncachedCall(Expression origin, ClassExpression sender, Expression receiver, Expression message, Expression arguments, MethodCallerMultiAdapter adapter, boolean safe, boolean spreadSafe, boolean implicitThis, boolean containsSpreadExpression) { |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| AsmClassGenerator acg = controller.getAcg(); |
| |
| // ensure variable arguments are read, not written |
| compileStack.pushLHS(false); |
| |
| // sender |
| sender.visit(acg); |
| |
| String methodName = getMethodName(message); |
| if (adapter == invokeMethodOnSuper && methodName != null) { |
| controller.getSuperMethodNames().add(methodName); // for MOP method |
| } |
| |
| // receiver |
| compileStack.pushImplicitThis(implicitThis); |
| receiver.visit(acg); |
| operandStack.box(); |
| compileStack.popImplicitThis(); |
| |
| int operandsToRemove = 2; |
| // message |
| if (message != null) { |
| message.visit(acg); |
| operandStack.box(); |
| operandsToRemove += 1; |
| } |
| |
| // 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 += 1) { |
| 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); |
| } |
| |
| /** |
| * if Class.forName(x) is recognized, make a direct method call |
| */ |
| protected boolean makeClassForNameCall(final Expression origin, final Expression receiver, final Expression message, final 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(final 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(final Expression message) { |
| String methodName = null; |
| if (message instanceof CastExpression) { |
| CastExpression msg = (CastExpression) message; |
| if (isStringType(msg.getType())) { |
| 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)) { |
| invokeClosure(call.getArguments(), call.getMethodAsString()); |
| } else { |
| if (isFunctionInterfaceCall(call)) { |
| call = transformToRealMethodCall(call); |
| } |
| Expression receiver = call.getObjectExpression(); |
| MethodCallerMultiAdapter adapter = invokeMethod; |
| if (isSuperExpression(receiver)) { |
| adapter = invokeMethodOnSuper; |
| } else if (isThisExpression(receiver)) { |
| adapter = invokeMethodOnCurrent; |
| } |
| if (isStaticInvocation(call)) { |
| adapter = invokeStaticMethod; |
| } |
| Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod()); |
| makeCall(call, receiver, messageName, call.getArguments(), adapter, call.isSafe(), call.isSpreadSafe(), call.isImplicitThis()); |
| } |
| } |
| |
| private static boolean isFunctionInterfaceCall(final MethodCallExpression call) { |
| if ("call".equals(call.getMethodAsString())) { |
| Expression objectExpression = call.getObjectExpression(); |
| if (!isThisExpression(objectExpression)) { |
| return isFunctionalInterface(objectExpression.getType()); |
| } |
| } |
| return false; |
| } |
| |
| private static MethodCallExpression transformToRealMethodCall(MethodCallExpression call) { |
| ClassNode type = call.getObjectExpression().getType(); |
| MethodNode methodNode = ClassHelper.findSAM(type); |
| |
| call = (MethodCallExpression) call.transformExpression(expression -> { |
| if (!(expression instanceof ConstantExpression)) { |
| return expression; |
| } |
| return new ConstantExpression(methodNode.getName()); |
| }); |
| call.setMethodTarget(methodNode); |
| return call; |
| } |
| |
| private boolean isClosureCall(final 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 (!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(final Expression arguments, final 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(final MethodCallExpression call) { |
| if (!isThisExpression(call.getObjectExpression())) return false; |
| if (controller.isStaticMethod()) return true; |
| return controller.isStaticContext() && !call.isImplicitThis(); |
| } |
| |
| public void writeInvokeStaticMethod(final StaticMethodCallExpression call) { |
| Expression receiver = new ClassExpression(call.getOwnerType()); |
| Expression messageName = new ConstantExpression(call.getMethod()); |
| makeCall(call, receiver, messageName, call.getArguments(), InvocationWriter.invokeStaticMethod, false, false, false); |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| public void writeInvokeConstructor(final ConstructorCallExpression call) { |
| if (writeDirectConstructorCall(call)) return; |
| if (writeAICCall (call)) return; |
| writeNormalConstructorCall(call); |
| } |
| |
| private boolean writeDirectConstructorCall(final ConstructorCallExpression call) { |
| if (!controller.isFastPath()) return false; |
| |
| OptimizingStatementWriter.StatementMeta meta = call.getNodeMetaData(OptimizingStatementWriter.StatementMeta.class); |
| ConstructorNode ctor = meta != null ? (ConstructorNode) meta.target : null; |
| if (ctor == null) return false; |
| |
| List<Expression> args = makeArgumentList(call.getArguments()).getExpressions(); |
| |
| loadArguments(args, ctor.getParameters()); |
| String ownerDescriptor = prepareConstructorCall(ctor); |
| finnishConstructorCall(ctor, ownerDescriptor, args.size()); |
| |
| return true; |
| } |
| |
| protected String prepareConstructorCall(final ConstructorNode cn) { |
| String type = BytecodeHelper.getClassInternalName(cn.getDeclaringClass()); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| mv.visitTypeInsn(NEW, type); |
| mv.visitInsn(DUP); |
| return type; |
| } |
| |
| protected void finnishConstructorCall(final ConstructorNode cn, final String ownerDescriptor, final int argsToRemove) { |
| String signature = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, cn.getParameters()); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| mv.visitMethodInsn(INVOKESPECIAL, ownerDescriptor, "<init>", signature, false); |
| |
| controller.getOperandStack().remove(argsToRemove); |
| controller.getOperandStack().push(cn.getDeclaringClass()); |
| } |
| |
| protected void writeNormalConstructorCall(final 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 receiver = new ClassExpression(call.getType()); |
| controller.getCallSiteWriter().makeCallSite(receiver, CallSiteWriter.CONSTRUCTOR, arguments, false, false, false, false); |
| } |
| |
| protected boolean writeAICCall(final ConstructorCallExpression call) { |
| if (!call.isUsingAnonymousInnerClass()) return false; |
| |
| ConstructorNode ctor = call.getType().getDeclaredConstructors().get(0); |
| |
| String ownerDescriptor = prepareConstructorCall(ctor); |
| |
| List<Expression> args = makeArgumentList(call.getArguments()).getExpressions(); |
| // 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); |
| int i = 0; Parameter[] params = ctor.getParameters(); |
| for (Expression arg : args) { |
| Parameter p = params[Math.min(i++, params.length)]; |
| if (arg instanceof VariableExpression) { |
| VariableExpression var = (VariableExpression) arg; |
| loadVariableWithReference(var); |
| } else { |
| arg.visit(controller.getAcg()); |
| if (arg instanceof CastExpression && !isPrimitiveType(arg.getType())) { |
| controller.getAcg().loadWrapper(arg); // GROOVY-6285, GROOVY-9244 |
| } |
| } |
| controller.getOperandStack().doGroovyCast(p.getType()); |
| } |
| controller.getCompileStack().popImplicitThis(); |
| finnishConstructorCall(ctor, ownerDescriptor, args.size()); |
| return true; |
| } |
| |
| private void loadVariableWithReference(final VariableExpression var) { |
| if (!var.isUseReferenceDirectly()) { |
| var.visit(controller.getAcg()); |
| } else { |
| ClosureWriter.loadReference(var.getName(), controller); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| public final void makeSingleArgumentCall(final Expression receiver, final String message, final Expression arguments) { |
| makeSingleArgumentCall(receiver, message, arguments, false); |
| } |
| |
| public void makeSingleArgumentCall(final Expression receiver, final String message, final Expression arguments, final boolean safe) { |
| controller.getCallSiteWriter().makeSingleArgumentCall(receiver, message, arguments, safe); |
| } |
| |
| public void writeSpecialConstructorCall(final ConstructorCallExpression call) { |
| controller.getCompileStack().pushInSpecialConstructorCall(); |
| |
| if (!controller.getClosureWriter().addGeneratedClosureConstructorCall(call)) { |
| ClassNode callType = controller.getClassNode(); |
| if (call.isSuperCall()) callType = callType.getSuperClass(); |
| List<ConstructorNode> constructors = sortConstructors(call, callType); |
| |
| if (!makeDirectConstructorCall(constructors, call, callType)) { |
| makeMOPBasedConstructorCall(constructors, call, callType); |
| } |
| } |
| |
| controller.getCompileStack().pop(); |
| } |
| |
| private static List<ConstructorNode> sortConstructors(final ConstructorCallExpression call, final ClassNode callType) { |
| // sort in a new list to prevent side effects |
| List<ConstructorNode> constructors = new ArrayList<>(callType.getDeclaredConstructors()); |
| constructors.sort((c0, c1) -> { |
| String descriptor0 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c0.getParameters()); |
| String descriptor1 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c1.getParameters()); |
| return descriptor0.compareTo(descriptor1); |
| }); |
| return constructors; |
| } |
| |
| private boolean makeDirectConstructorCall(final List<ConstructorNode> constructors, final ConstructorCallExpression call, final ClassNode callType) { |
| if (!controller.isConstructor()) return false; |
| |
| Expression arguments = call.getArguments(); |
| List<Expression> argumentList; |
| if (arguments instanceof TupleExpression) { |
| argumentList = ((TupleExpression) arguments).getExpressions(); |
| } else { |
| argumentList = new ArrayList<>(); |
| argumentList.add(arguments); |
| } |
| for (Expression argument : argumentList) { |
| if (argument instanceof SpreadExpression) return false; |
| } |
| |
| ConstructorNode ctor = null; |
| ConstructorNode varg = null; |
| int nArguments = argumentList.size(); |
| for (ConstructorNode constructor : constructors) { |
| int nParameters = constructor.getParameters().length; |
| if (nArguments == nParameters) { |
| if (ctor == null) ctor = constructor; |
| else return false; // ambiguous match |
| } else if (isVargs(constructor.getParameters()) |
| && (nArguments == nParameters - 1 || nArguments > nParameters)) { |
| if (varg == null) varg = constructor; |
| else return false; // ambiguous match |
| } |
| } |
| if (ctor == null) ctor = varg; |
| if (ctor == null) return false; |
| |
| MethodVisitor mv = controller.getMethodVisitor(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| int mark = operandStack.getStackLength(); |
| loadArguments(argumentList, ctor.getParameters()); |
| operandStack.remove(operandStack.getStackLength() - mark); |
| String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, ctor.getParameters()); |
| mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callType), "<init>", descriptor, false); |
| |
| return true; |
| } |
| |
| private void makeMOPBasedConstructorCall(final List<ConstructorNode> constructors, final ConstructorCallExpression call, final 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<Integer,ConstructorNode> 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<Integer> hashIt = sortedConstructors.keySet().iterator(); |
| Iterator<ConstructorNode> constructorIt = sortedConstructors.values().iterator(); |
| for (int i = 0, n = targets.length; i < n; i += 1) { |
| 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 (Label target : targets) { |
| mv.visitLabel(target); |
| // 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 -= 1; |
| } |
| for (int p = 0; p < lengthWithoutVargs; p += 1) { |
| 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), "<init>", 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", "<init>", "(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(final OperandStack operandStack, final MethodVisitor mv, final Parameter[] parameters, final 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); |
| } |
| |
| /** |
| * Converts sourceType to a non-primitive by using Groovy casting. |
| * sourceType might be a primitive |
| * This might be done using SBA#castToType |
| */ |
| public void castToNonPrimitiveIfNecessary(final ClassNode sourceType, final ClassNode targetType) { |
| OperandStack os = controller.getOperandStack(); |
| ClassNode boxedType = os.box(); |
| if (WideningCategories.implementsInterfaceOrSubclassOf(boxedType, targetType)) return; |
| MethodVisitor mv = controller.getMethodVisitor(); |
| if (isClassType(targetType)) { |
| castToClassMethod.call(mv); |
| } else if (isStringType(targetType)) { |
| castToStringMethod.call(mv); |
| } else if (targetType.isDerivedFrom(ClassHelper.Enum_Type)) { |
| (new ClassExpression(targetType)).visit(controller.getAcg()); |
| os.remove(1); |
| castToEnumMethod.call(mv); |
| BytecodeHelper.doCast(mv, targetType); |
| } else { |
| (new ClassExpression(targetType)).visit(controller.getAcg()); |
| os.remove(1); |
| castToTypeMethod.call(mv); |
| } |
| } |
| |
| public void castNonPrimitiveToBool(final ClassNode last) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| BytecodeHelper.unbox(mv, ClassHelper.boolean_TYPE); |
| } |
| |
| public void coerce(final ClassNode from, final ClassNode target) { |
| if (from.isDerivedFrom(target)) return; |
| MethodVisitor mv = controller.getMethodVisitor(); |
| OperandStack os = controller.getOperandStack(); |
| os.box(); |
| (new ClassExpression(target)).visit(controller.getAcg()); |
| os.remove(1); |
| asTypeMethod.call(mv); |
| BytecodeHelper.doCast(mv,target); |
| os.replace(target); |
| } |
| } |