From 222874267abb88b6a65479eebd975bc4aab68613 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Fri, 26 Nov 2021 14:49:57 -0600 Subject: [PATCH] GROOVY-8693, GROOVY-10380, GROOVY-10381 --- .../tests/xform/StaticCompilationTests.java | 141 +++ .../core/tests/xform/TypeCheckedTests.java | 34 + base/org.codehaus.groovy25/.checkstyle | 1 + .../groovy/classgen/asm/InvocationWriter.java | 959 ++++++++++++++++++ base/org.codehaus.groovy30/.checkstyle | 3 +- .../groovy/classgen/asm/InvocationWriter.java | 908 +++++++++++++++++ base/org.codehaus.groovy40/.checkstyle | 1 + .../groovy/classgen/asm/InvocationWriter.java | 896 ++++++++++++++++ 8 files changed, 2941 insertions(+), 2 deletions(-) create mode 100644 base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java create mode 100644 base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java create mode 100644 base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 7555a40ec8..83760bca28 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -2603,6 +2603,40 @@ public void testCompileStatic8686b() { "----------\n"); } + @Test + public void testCompileStatic8693() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "class C extends p.A {\n" + + " void m() {\n" + + " super.m()\n" + // StackOverflowError + " }\n" + + " void test() {\n" + + " m()\n" + + " }\n" + + "}\n" + + "new C().test()\n", + + "p/A.java", + "package p;\n" + + "public abstract class A implements I {\n" + + "}\n", + + "p/I.java", + "package p;\n" + + "public interface I {\n" + + " default void m() {\n" + + " System.out.print(\"works\");\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } + @Test public void testCompileStatic8816() { //@formatter:off @@ -6236,6 +6270,44 @@ public void testCompileStatic9893a() { runConformTest(sources, "String"); } + @Test(expected = AssertionError.class) + public void testCompileStatic9909() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import p.*\n" + + "@groovy.transform.CompileStatic\n" + + "class C implements A, B {\n" + + " void m() {\n" + + " A.super.m()\n" + + " }\n" + + " void test() {\n" + + " m()\n" + + " }\n" + + "}\n" + + "new C().test()\n", + + "p/A.java", + "package p;\n" + + "public interface A {\n" + + " default void m() {\n" + + " System.out.print(\"A\");\n" + + " }\n" + + "}\n", + + "p/B.java", + "package p;\n" + + "public interface B {\n" + + " default void m() {\n" + + " System.out.print(\"B\");\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources, "A"); + } + @Test public void testCompileStatic9918() { //@formatter:off @@ -6786,4 +6858,73 @@ public void testCompileStatic10379() { runConformTest(sources, "string"); } + + @Test + public void testCompileStatic10380() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "class C extends p.A {\n" + + " void test() {\n" + + " m()\n" + // IncompatibleClassChangeError: Found class C, but interface was expected + " }\n" + + "}\n" + + "new C().test()\n", + + "p/A.groovy", + "package p\n" + + "abstract class A implements I {\n" + + "}\n", + + "p/I.java", + "package p;\n" + + "interface I {\n" + + " default void m() {\n" + + " System.out.print(\"works\");\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } + + @Test + public void testCompileStatic10381() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "class C implements p.A, p.B {\n" + + " void test() {\n" + + " m()\n" + + " }\n" + + "}\n" + + "new C().test()\n", + + "p/A.java", + "package p;\n" + + "public interface A {\n" + + " default void m() {\n" + + " }\n" + + "}\n", + + "p/B.java", + "package p;\n" + + "public interface B {\n" + + " default void m() {\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 2)\n" + + "\tclass C implements p.A, p.B {\n" + + "\t ^\n" + + "Duplicate default methods named m with the parameters () and () are inherited from the types A and B\n" + + "----------\n"); + } } diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java index b671c2b8b3..bc32c750c8 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java @@ -1352,6 +1352,40 @@ public void testTypeChecked8202() { runConformTest(sources, "foobarbaznullnullnull"); } + @Test(expected = AssertionError.class) + public void testTypeChecked8693() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "class C extends p.A {\n" + + " void m() {\n" + + " super.m()\n" + // MissingMethodException + " }\n" + + " void test() {\n" + + " m()\n" + + " }\n" + + "}\n" + + "new C().test()\n", + + "p/A.java", + "package p;\n" + + "public abstract class A implements I {\n" + + "}\n", + + "p/I.java", + "package p;\n" + + "public interface I {\n" + + " default void m() {\n" + + " System.out.print(\"works\");\n" + + " }\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } + @Test public void testTypeChecked8909() { //@formatter:off diff --git a/base/org.codehaus.groovy25/.checkstyle b/base/org.codehaus.groovy25/.checkstyle index 9f4c800e92..24edcd2660 100644 --- a/base/org.codehaus.groovy25/.checkstyle +++ b/base/org.codehaus.groovy25/.checkstyle @@ -48,6 +48,7 @@ + diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java new file mode 100644 index 0000000000..9ec34ee7bb --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java @@ -0,0 +1,959 @@ +/* + * 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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +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.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.isPrimitiveType; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ACC_FINAL; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACONST_NULL; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ATHROW; +import static groovyjarjarasm.asm.Opcodes.CHECKCAST; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.DUP2_X1; +import static groovyjarjarasm.asm.Opcodes.DUP_X1; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; +import static groovyjarjarasm.asm.Opcodes.POP; +import static groovyjarjarasm.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 final 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 sender = controller.getClassNode(); + if (isSuperExpression(receiver) || (isThisExpression(receiver) && !implicitThis)) { + while (sender.getOuterClass() != null && sender.getSuperClass() == ClassHelper.CLOSURE_TYPE) { + sender = sender.getOuterClass(); + } + if (isSuperExpression(receiver)) { + sender = sender.getSuperClass(); // GROOVY-4035 + implicitThis = false; // prevent recursion + safe = false; // GROOVY-6045 + } + } + + makeCall(origin, new ClassExpression(sender), receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); + } + + protected boolean writeDirectMethodCall(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args) { + if (target == null) return false; + + ClassNode declaringClass = target.getDeclaringClass(); + String methodName = target.getName(); + int opcode = INVOKEVIRTUAL; + if (target.isStatic()) { + opcode = INVOKESTATIC; + /* GRECLIPSE edit -- GROOVY-8693, et al. + } else if (declaringClass.isInterface()) { + opcode = INVOKEINTERFACE; + } else if (target.isPrivate() || ((receiver instanceof VariableExpression && ((VariableExpression) receiver).isSuperExpression()))) { + opcode = INVOKESPECIAL;*/ + } else if (isSuperExpression(receiver)) { + opcode = INVOKESPECIAL; + } else if (declaringClass.isInterface()) { + opcode = INVOKEINTERFACE; + // GRECLIPSE end + } + + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + + // 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++; + } + } + + ClassNode receiverType; + if (receiver == null) { + receiverType = declaringClass; + } else { + receiverType = controller.getTypeChooser().resolveType(receiver, classNode); + if (isClassClassNodeWrappingConcreteType(receiverType) && target.isStatic()) { + receiverType = receiverType.getGenericsTypes()[0].getType(); + } + } + + int stackLen = operandStack.getStackLength(); + /* GRECLIPSE edit -- GROOVY-8693, GROOVY-10380 + String owner = BytecodeHelper.getClassInternalName(declaringClass); + */ + ClassNode ownerClass = declaringClass; + if (opcode == INVOKESPECIAL) { + ownerClass = receiverType; + } else + // GRECLIPSE end + if (opcode == INVOKEVIRTUAL && declaringClass.equals(ClassHelper.OBJECT_TYPE)) { + // 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; + if (!receiverType.equals(operandStack.getTopOperand())) { + mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(ownerClass)); + } + } else if (opcode != INVOKESPECIAL && (declaringClass.getModifiers() & (ACC_FINAL | ACC_PUBLIC)) == 0 && !receiverType.equals(declaringClass) + && (declaringClass.isInterface() ? receiverType.implementsInterface(declaringClass) : receiverType.isDerivedFrom(declaringClass))) { + /* GRECLIPSE edit + // GROOVY-6962, GROOVY-9955: method declared by inaccessible class + owner = BytecodeHelper.getClassInternalName(receiverType); + */ + if (declaringClass.isInterface() && !receiverType.isInterface()) opcode = INVOKEVIRTUAL; + ownerClass = receiverType; + // GRECLIPSE end + } + + loadArguments(args.getExpressions(), target.getParameters()); + + String descriptor = BytecodeHelper.getMethodDescriptor(target.getReturnType(), target.getParameters()); + /* GRECLIPSE edit + mv.visitMethodInsn(opcode, owner, methodName, descriptor, declaringClass.isInterface()); + */ + mv.visitMethodInsn(opcode, BytecodeHelper.getClassInternalName(ownerClass), methodName, descriptor, ownerClass.isInterface()); + // GRECLIPSE end + ClassNode returnType = target.getReturnType().redirect(); + if (returnType == ClassHelper.VOID_TYPE) { + returnType = ClassHelper.OBJECT_TYPE; + mv.visitInsn(ACONST_NULL); + } + argumentsToRemove += (operandStack.getStackLength() - stackLen); + controller.getOperandStack().remove(argumentsToRemove); + controller.getOperandStack().push(returnType); + 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(); + 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 (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 (!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 (!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 - + - diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java new file mode 100644 index 0000000000..9a56714703 --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java @@ -0,0 +1,908 @@ +/* + * 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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +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.isFunctionalInterface; +import static org.codehaus.groovy.ast.ClassHelper.isGeneratedFunction; +import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ACC_FINAL; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACONST_NULL; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ATHROW; +import static groovyjarjarasm.asm.Opcodes.CHECKCAST; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.DUP2_X1; +import static groovyjarjarasm.asm.Opcodes.DUP_X1; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; +import static groovyjarjarasm.asm.Opcodes.POP; +import static groovyjarjarasm.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 = controller.getClassNode(); + if (isSuperExpression(receiver) || (isThisExpression(receiver) && !implicitThis)) { + while (isGeneratedFunction(sender)) { + sender = sender.getOuterClass(); + } + if (isSuperExpression(receiver)) { + sender = sender.getSuperClass(); // GROOVY-4035 + implicitThis = false; // prevent recursion + safe = false; // GROOVY-6045 + } + } + + makeCall(origin, new ClassExpression(sender), receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); + } + + protected boolean writeDirectMethodCall(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args) { + if (target == null) return false; + + ClassNode declaringClass = target.getDeclaringClass(); + String methodName = target.getName(); + int opcode = INVOKEVIRTUAL; + if (target.isStatic()) { + opcode = INVOKESTATIC; + /* GRECLIPSE edit -- GROOVY-8693, et al. + } else if (declaringClass.isInterface()) { + opcode = INVOKEINTERFACE; + } else if (target.isPrivate() || isSuperExpression(receiver)) { + opcode = INVOKESPECIAL;*/ + } else if (isSuperExpression(receiver)) { + opcode = INVOKESPECIAL; + } else if (declaringClass.isInterface()) { + opcode = INVOKEINTERFACE; + // GRECLIPSE end + } + + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); + ClassNode classNode = controller.getClassNode(); + + // 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.getOuterClass() != null + && !classNode.isDerivedFrom(declaringClass) + && !classNode.implementsInterface(declaringClass)) { + // we are calling an outer class method + compileStack.pushImplicitThis(false); + if (controller.isInGeneratedFunction()) { + new VariableExpression("thisObject").visit(controller.getAcg()); + } else { // TODO: handle implicitThis && !isThisExpression(receiver) + 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 += 1; + } else { + mv.visitIntInsn(ALOAD, 0); + operandStack.push(classNode); + argumentsToRemove += 1; + } + } + + ClassNode receiverType; + if (receiver == null) { + receiverType = declaringClass; + } else { + receiverType = controller.getTypeChooser().resolveType(receiver, classNode); + if (isClassClassNodeWrappingConcreteType(receiverType) && target.isStatic()) { + receiverType = receiverType.getGenericsTypes()[0].getType(); + } + } + + int stackLen = operandStack.getStackLength(); + /* GRECLIPSE edit -- GROOVY-8693, GROOVY-10380 + String owner = BytecodeHelper.getClassInternalName(declaringClass); + */ + ClassNode ownerClass = declaringClass; + if (opcode == INVOKESPECIAL) { + ownerClass = receiverType; + } else + // GRECLIPSE end + if (opcode == INVOKEVIRTUAL && declaringClass.equals(ClassHelper.OBJECT_TYPE)) { + // 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; + if (!receiverType.equals(operandStack.getTopOperand())) { + mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(ownerClass)); + } + } else if (opcode != INVOKESPECIAL && (declaringClass.getModifiers() & (ACC_FINAL | ACC_PUBLIC)) == 0 && !receiverType.equals(declaringClass) + && (declaringClass.isInterface() ? receiverType.implementsInterface(declaringClass) : receiverType.isDerivedFrom(declaringClass))) { + /* GRECLIPSE edit + // GROOVY-6962, GROOVY-9955: method declared by inaccessible class + owner = BytecodeHelper.getClassInternalName(receiverType); + */ + if (declaringClass.isInterface() && !receiverType.isInterface()) opcode = INVOKEVIRTUAL; + ownerClass = receiverType; + // GRECLIPSE end + } + + loadArguments(args.getExpressions(), target.getParameters()); + + String descriptor = BytecodeHelper.getMethodDescriptor(target.getReturnType(), target.getParameters()); + /* GRECLIPSE edit + mv.visitMethodInsn(opcode, owner, methodName, descriptor, declaringClass.isInterface()); + */ + mv.visitMethodInsn(opcode, BytecodeHelper.getClassInternalName(ownerClass), methodName, descriptor, ownerClass.isInterface()); + // GRECLIPSE end + ClassNode returnType = target.getReturnType().redirect(); + if (returnType == ClassHelper.VOID_TYPE) { + returnType = ClassHelper.OBJECT_TYPE; + mv.visitInsn(ACONST_NULL); + } + argumentsToRemove += (operandStack.getStackLength() - stackLen); + controller.getOperandStack().remove(argumentsToRemove); + controller.getOperandStack().push(returnType); + return true; + } + + private boolean lastIsArray(final List argumentList, final int pos) { + Expression last = argumentList.get(pos); + ClassNode type = controller.getTypeChooser().resolveType(last, controller.getClassNode()); + return type.isArray(); + } + + // load arguments + protected void loadArguments(final List argumentList, final 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(); + controller.setMethodVisitor(mv); + // varg call + // first parameters as usual + for (int i = 0, n = para.length - 1; i < n; i += 1) { + 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, n = argumentList.size(); i < n; i += 1) { + lastParams.add(argumentList.get(i)); + } + ArrayExpression array = new ArrayExpression( + lastParaType.getComponentType(), + lastParams + ); + array.visit(acg); + // adjust stack length + while (operandStack.getStackLength() < stackLen) { + operandStack.push(ClassHelper.OBJECT_TYPE); + } + if (argumentList.size() == para.length - 1) { + operandStack.remove(1); + } + } else { + for (int i = 0, n = argumentList.size(); i < n; i += 1) { + argumentList.get(i).visit(acg); + operandStack.doGroovyCast(para[i].getType()); + } + } + } + + 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; + + // 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); + } + + OptimizingStatementWriter.StatementMeta meta = null; + if (origin != null) meta = origin.getNodeMetaData(OptimizingStatementWriter.StatementMeta.class); + MethodNode mn = null; + if (meta != null) mn = meta.target; + + if (writeDirectMethodCall(mn, true, null, args)) return true; + } + } + + if (containsSpreadExpression) return false; + if (origin instanceof MethodCallExpression) { + MethodCallExpression mce = (MethodCallExpression) origin; + MethodNode target = mce.getMethodTarget(); + return writeDirectMethodCall(target, 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) { + 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); + } + + String methodName = getMethodName(message); + if (adapter == invokeMethodOnSuper && methodName != null) { + controller.getSuperMethodNames().add(methodName); + } + + // 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); + } + + 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(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 (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 { + if (isFunctionInterfaceCall(call)) { + call = transformToRealMethodCall(call); + } + MethodCallerMultiAdapter adapter = invokeMethod; + Expression objectExpression = call.getObjectExpression(); + if (isSuperExpression(objectExpression)) { + adapter = invokeMethodOnSuper; + } else if (isThisExpression(objectExpression)) { + adapter = invokeMethodOnCurrent; + } + if (isStaticInvocation(call)) { + adapter = invokeStaticMethod; + } + Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod()); + makeCall(call, objectExpression, 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); + } + + private boolean writeDirectConstructorCall(final ConstructorCallExpression call) { + if (!controller.isFastPath()) return false; + + OptimizingStatementWriter.StatementMeta meta = call.getNodeMetaData(OptimizingStatementWriter.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(final ConstructorNode cn) { + String owner = BytecodeHelper.getClassInternalName(cn.getDeclaringClass()); + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitTypeInsn(NEW, owner); + mv.visitInsn(DUP); + return owner; + } + + protected void finnishConstructorCall(final ConstructorNode cn, final String ownerDescriptor, final 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(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); + } + + public void writeInvokeConstructor(final ConstructorCallExpression call) { + if (writeDirectConstructorCall(call)) return; + if (writeAICCall(call)) return; + writeNormalConstructorCall(call); + } + + protected boolean writeAICCall(final 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, n = params.length; i < n; i += 1) { + Parameter p = params[i]; + Expression arg = args.get(i); + if (arg instanceof VariableExpression) { + VariableExpression var = (VariableExpression) arg; + loadVariableWithReference(var); + } else { + arg.visit(controller.getAcg()); + } + os.doGroovyCast(p.getType()); + } + controller.getCompileStack().popImplicitThis(); + finnishConstructorCall(cn, 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(); + visitSpecialConstructorCall(call); + controller.getCompileStack().pop(); + } + + private void visitSpecialConstructorCall(final ConstructorCallExpression call) { + if (controller.getClosureWriter().addGeneratedClosureConstructorCall(call)) return; + ClassNode callNode = controller.getClassNode(); + if (call.isSuperCall()) callNode = callNode.getSuperClass(); + List constructors = sortConstructors(call, callNode); + if (!makeDirectConstructorCall(constructors, call, callNode)) { + makeMOPBasedConstructorCall(constructors, call, callNode); + } + } + + private static List sortConstructors(final ConstructorCallExpression call, final ClassNode callNode) { + // sort in a new list to prevent side effects + List constructors = new ArrayList<>(callNode.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 constructors, final ConstructorCallExpression call, final 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, n = params.length; i < n; i += 1) { + Expression expression = argumentList.get(i); + expression.visit(controller.getAcg()); + if (!isNullConstant(expression)) { + operandStack.doGroovyCast(params[i].getType()); + } + operandStack.remove(1); + } + String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, params); + mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "", descriptor, false); + + return true; + } + + private void makeMOPBasedConstructorCall(final List 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 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, 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), "", 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(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); + } + + // we match only on the number of arguments, not anything else + private static ConstructorNode getMatchingConstructor(final List constructors, final List argumentList) { + ConstructorNode lastMatch = null; + for (ConstructorNode cn : constructors) { + Parameter[] params = cn.getParameters(); + // if number of parameters does not match we have no match + if (argumentList.size() != params.length) continue; + if (lastMatch == null) { + lastMatch = cn; + } else { + // we already had a match so we don't make a direct call at all + return null; + } + } + return lastMatch; + } + + /** + * 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 (ClassHelper.CLASS_Type.equals(targetType)) { + castToClassMethod.call(mv); + } else if (ClassHelper.STRING_TYPE.equals(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); + } +} diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle index 772e3818ac..9be20af488 100644 --- a/base/org.codehaus.groovy40/.checkstyle +++ b/base/org.codehaus.groovy40/.checkstyle @@ -39,6 +39,7 @@ + diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java new file mode 100644 index 0000000000..a9dfaa5d30 --- /dev/null +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/InvocationWriter.java @@ -0,0 +1,896 @@ +/* + * 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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +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.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ACC_FINAL; +import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; +import static groovyjarjarasm.asm.Opcodes.ACONST_NULL; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ATHROW; +import static groovyjarjarasm.asm.Opcodes.CHECKCAST; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.DUP2_X1; +import static groovyjarjarasm.asm.Opcodes.DUP_X1; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE; +import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.INVOKEVIRTUAL; +import static groovyjarjarasm.asm.Opcodes.NEW; +import static groovyjarjarasm.asm.Opcodes.POP; +import static groovyjarjarasm.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)) { + sender = controller.getThisType(); + if (isSuperExpression(receiver)) { + sender = sender.getSuperClass(); // GROOVY-4035 + implicitThis = false; // prevent recursion + safe = false; // GROOVY-6045 + } + } else { + sender = controller.getClassNode(); + } + + makeCall(origin, new ClassExpression(sender), receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); + } + + protected boolean writeDirectMethodCall(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args) { + if (target == null) 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 + && 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)) { + opcode = INVOKESPECIAL; + } else if (declaringClass.isInterface()) { + opcode = INVOKEINTERFACE; + } else { + opcode = INVOKEVIRTUAL; + } + + ClassNode ownerClass = declaringClass; + if (opcode == INVOKESPECIAL) { + ownerClass = receiverType; // GROOVY-8693 + } 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; + } + + loadArguments(args.getExpressions(), target.getParameters()); + + String ownerName = BytecodeHelper.getClassInternalName(ownerClass), methodName = target.getName(); + String signature = BytecodeHelper.getMethodDescriptor(target.getReturnType(), target.getParameters()); + mv.visitMethodInsn(opcode, ownerName, methodName, signature, ownerClass.isInterface()); + ClassNode returnType = target.getReturnType(); + 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; + } + + /** + * 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 lastIsArray(final List argumentList, final int pos) { + Expression last = argumentList.get(pos); + ClassNode type = controller.getTypeChooser().resolveType(last, controller.getClassNode()); + return type.isArray(); + } + + // load arguments + protected void loadArguments(final List argumentList, final 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(); + controller.setMethodVisitor(mv); + // varg call + // first parameters as usual + for (int i = 0, n = para.length - 1; i < n; i += 1) { + 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, n = argumentList.size(); i < n; i += 1) { + lastParams.add(argumentList.get(i)); + } + ArrayExpression array = new ArrayExpression( + lastParaType.getComponentType(), + lastParams + ); + array.visit(acg); + // adjust stack length + while (operandStack.getStackLength() < stackLen) { + operandStack.push(ClassHelper.OBJECT_TYPE); + } + if (argumentList.size() == para.length - 1) { + operandStack.remove(1); + } + } else { + for (int i = 0, n = argumentList.size(); i < n; i += 1) { + argumentList.get(i).visit(acg); + operandStack.doGroovyCast(para[i].getType()); + } + } + } + + 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; + + // 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); + } + + OptimizingStatementWriter.StatementMeta meta = null; + if (origin != null) meta = origin.getNodeMetaData(OptimizingStatementWriter.StatementMeta.class); + MethodNode mn = null; + if (meta != null) mn = meta.target; + + if (writeDirectMethodCall(mn, true, null, args)) return true; + } + } + + if (containsSpreadExpression) return false; + if (origin instanceof MethodCallExpression) { + MethodCallExpression mce = (MethodCallExpression) origin; + MethodNode target = mce.getMethodTarget(); + return writeDirectMethodCall(target, 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) { + 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); + } + + String methodName = getMethodName(message); + if (adapter == invokeMethodOnSuper && methodName != null) { + controller.getSuperMethodNames().add(methodName); + } + + // 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); + } + + 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(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)) { + // let's invoke the closure method + invokeClosure(call.getArguments(), call.getMethodAsString()); + } else { + if (isFunctionInterfaceCall(call)) { + call = transformToRealMethodCall(call); + } + MethodCallerMultiAdapter adapter = invokeMethod; + Expression objectExpression = call.getObjectExpression(); + if (isSuperExpression(objectExpression)) { + adapter = invokeMethodOnSuper; + } else if (isThisExpression(objectExpression)) { + adapter = invokeMethodOnCurrent; + } + if (isStaticInvocation(call)) { + adapter = invokeStaticMethod; + } + Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod()); + makeCall(call, objectExpression, 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); + } + + private boolean writeDirectConstructorCall(final ConstructorCallExpression call) { + if (!controller.isFastPath()) return false; + + OptimizingStatementWriter.StatementMeta meta = call.getNodeMetaData(OptimizingStatementWriter.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(final ConstructorNode cn) { + String owner = BytecodeHelper.getClassInternalName(cn.getDeclaringClass()); + MethodVisitor mv = controller.getMethodVisitor(); + mv.visitTypeInsn(NEW, owner); + mv.visitInsn(DUP); + return owner; + } + + protected void finnishConstructorCall(final ConstructorNode cn, final String ownerDescriptor, final 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(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); + } + + public void writeInvokeConstructor(final ConstructorCallExpression call) { + if (writeDirectConstructorCall(call)) return; + if (writeAICCall(call)) return; + writeNormalConstructorCall(call); + } + + protected boolean writeAICCall(final 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, n = params.length; i < n; i += 1) { + Parameter p = params[i]; + Expression arg = args.get(i); + if (arg instanceof VariableExpression) { + VariableExpression var = (VariableExpression) arg; + loadVariableWithReference(var); + } else { + arg.visit(controller.getAcg()); + } + os.doGroovyCast(p.getType()); + } + controller.getCompileStack().popImplicitThis(); + finnishConstructorCall(cn, 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(); + visitSpecialConstructorCall(call); + controller.getCompileStack().pop(); + } + + private void visitSpecialConstructorCall(final ConstructorCallExpression call) { + if (controller.getClosureWriter().addGeneratedClosureConstructorCall(call)) return; + ClassNode callNode = controller.getClassNode(); + if (call.isSuperCall()) callNode = callNode.getSuperClass(); + List constructors = sortConstructors(call, callNode); + if (!makeDirectConstructorCall(constructors, call, callNode)) { + makeMOPBasedConstructorCall(constructors, call, callNode); + } + } + + private static List sortConstructors(final ConstructorCallExpression call, final ClassNode callNode) { + // sort in a new list to prevent side effects + List constructors = new ArrayList<>(callNode.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 constructors, final ConstructorCallExpression call, final 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, n = params.length; i < n; i += 1) { + Expression expression = argumentList.get(i); + expression.visit(controller.getAcg()); + if (!isNullConstant(expression)) { + operandStack.doGroovyCast(params[i].getType()); + } + operandStack.remove(1); + } + String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, params); + mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "", descriptor, false); + + return true; + } + + private void makeMOPBasedConstructorCall(final List 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 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, 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), "", 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(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); + } + + // we match only on the number of arguments, not anything else + private static ConstructorNode getMatchingConstructor(final List constructors, final List argumentList) { + ConstructorNode lastMatch = null; + for (ConstructorNode cn : constructors) { + Parameter[] params = cn.getParameters(); + // if number of parameters does not match we have no match + if (argumentList.size() != params.length) continue; + if (lastMatch == null) { + lastMatch = cn; + } else { + // we already had a match so we don't make a direct call at all + return null; + } + } + return lastMatch; + } + + /** + * 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); + } +}