|
25 | 25 | import org.codehaus.groovy.ast.expr.ConstructorCallExpression; |
26 | 26 | import org.codehaus.groovy.ast.expr.EmptyExpression; |
27 | 27 | import org.codehaus.groovy.ast.expr.Expression; |
| 28 | +import org.codehaus.groovy.ast.expr.MethodCallExpression; |
28 | 29 | import org.codehaus.groovy.ast.expr.PropertyExpression; |
29 | 30 | import org.codehaus.groovy.ast.tools.WideningCategories; |
30 | 31 | import org.codehaus.groovy.classgen.AsmClassGenerator; |
|
42 | 43 | import java.lang.invoke.CallSite; |
43 | 44 | import java.lang.invoke.MethodHandles.Lookup; |
44 | 45 | import java.lang.invoke.MethodType; |
| 46 | +import java.util.ArrayList; |
45 | 47 | import java.util.List; |
46 | 48 |
|
| 49 | +import static org.apache.groovy.ast.tools.ExpressionUtils.isSuperExpression; |
47 | 50 | import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression; |
48 | 51 | import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; |
49 | 52 | import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; |
|
54 | 57 | import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX; |
55 | 58 | import static org.codehaus.groovy.classgen.asm.BytecodeHelper.doCast; |
56 | 59 | import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getTypeDescription; |
57 | | -import static org.codehaus.groovy.vmplugin.v8.IndyInterface.GROOVY_OBJECT; |
58 | | -import static org.codehaus.groovy.vmplugin.v8.IndyInterface.IMPLICIT_THIS; |
59 | | -import static org.codehaus.groovy.vmplugin.v8.IndyInterface.SAFE_NAVIGATION; |
60 | | -import static org.codehaus.groovy.vmplugin.v8.IndyInterface.SPREAD_CALL; |
61 | | -import static org.codehaus.groovy.vmplugin.v8.IndyInterface.THIS_CALL; |
62 | 60 | import static org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType.CAST; |
63 | 61 | import static org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType.GET; |
64 | 62 | import static org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType.INIT; |
65 | 63 | import static org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType.INTERFACE; |
66 | 64 | import static org.codehaus.groovy.vmplugin.v8.IndyInterface.CallType.METHOD; |
| 65 | +import static org.codehaus.groovy.vmplugin.v8.IndyInterface.GROOVY_OBJECT; |
| 66 | +import static org.codehaus.groovy.vmplugin.v8.IndyInterface.IMPLICIT_THIS; |
| 67 | +import static org.codehaus.groovy.vmplugin.v8.IndyInterface.SAFE_NAVIGATION; |
| 68 | +import static org.codehaus.groovy.vmplugin.v8.IndyInterface.SPREAD_CALL; |
| 69 | +import static org.codehaus.groovy.vmplugin.v8.IndyInterface.THIS_CALL; |
67 | 70 | import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; |
68 | 71 | import static org.objectweb.asm.Opcodes.IFNULL; |
69 | 72 |
|
@@ -113,12 +116,91 @@ private String prepareIndyCall(final Expression receiver, final boolean implicit |
113 | 116 |
|
114 | 117 | // load normal receiver as first argument |
115 | 118 | compileStack.pushImplicitThis(implicitThis); |
116 | | - receiver.visit(controller.getAcg()); |
| 119 | + // GROOVY-7785: use iterative approach to avoid stack overflow for chained method calls |
| 120 | + visitReceiverOfMethodCall(receiver); |
117 | 121 | compileStack.popImplicitThis(); |
118 | 122 |
|
119 | 123 | return "(" + getTypeDescription(operandStack.getTopOperand()); |
120 | 124 | } |
121 | 125 |
|
| 126 | + /** |
| 127 | + * Visit receiver expression iteratively to avoid stack overflow for deeply nested method call chains. |
| 128 | + * For chained calls like a().b().c()...z(), the AST forms a deep right-recursive structure where |
| 129 | + * each method call's receiver is another method call. This method flattens the chain and processes |
| 130 | + * it iteratively from the innermost receiver outward. |
| 131 | + */ |
| 132 | + private void visitReceiverOfMethodCall(final Expression receiver) { |
| 133 | + // Collect chain of simple method calls that can use indy optimization |
| 134 | + List<MethodCallExpression> chain = new ArrayList<>(); |
| 135 | + Expression current = receiver; |
| 136 | + while (current instanceof MethodCallExpression mce |
| 137 | + && !mce.isSpreadSafe() && !mce.isImplicitThis() |
| 138 | + && !isSuperExpression(mce.getObjectExpression()) |
| 139 | + && !isThisExpression(mce.getObjectExpression())) { |
| 140 | + String name = getMethodName(mce.getMethod()); |
| 141 | + if (name == null || "call".equals(name)) break; // dynamic name or functional interface call |
| 142 | + chain.add(mce); |
| 143 | + current = mce.getObjectExpression(); |
| 144 | + } |
| 145 | + |
| 146 | + if (chain.isEmpty()) { |
| 147 | + receiver.visit(controller.getAcg()); |
| 148 | + return; |
| 149 | + } |
| 150 | + |
| 151 | + // Visit innermost receiver, then process chain from innermost to outermost |
| 152 | + current.visit(controller.getAcg()); |
| 153 | + AsmClassGenerator acg = controller.getAcg(); |
| 154 | + for (int i = chain.size() - 1; i >= 0; i--) { |
| 155 | + MethodCallExpression mce = chain.get(i); |
| 156 | + acg.onLineNumber(mce, "visitMethodCallExpression: \"" + mce.getMethod() + "\":"); |
| 157 | + finishIndyCallForChain(mce); |
| 158 | + controller.getAssertionWriter().record(mce.getMethod()); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + /** Complete an indy call for a chained method, assuming receiver is already on stack. */ |
| 163 | + private void finishIndyCallForChain(final MethodCallExpression call) { |
| 164 | + OperandStack operandStack = controller.getOperandStack(); |
| 165 | + AsmClassGenerator acg = controller.getAcg(); |
| 166 | + Expression arguments = call.getArguments(); |
| 167 | + boolean safe = call.isSafe(); |
| 168 | + |
| 169 | + StringBuilder sig = new StringBuilder("(" + getTypeDescription(operandStack.getTopOperand())); |
| 170 | + Label end = null; |
| 171 | + if (safe && !isPrimitiveType(operandStack.getTopOperand())) { |
| 172 | + operandStack.dup(); |
| 173 | + end = operandStack.jump(IFNULL); |
| 174 | + } |
| 175 | + |
| 176 | + int nArgs = 1; |
| 177 | + List<Expression> args = makeArgumentList(arguments).getExpressions(); |
| 178 | + boolean spread = AsmClassGenerator.containsSpreadExpression(arguments); |
| 179 | + if (spread) { |
| 180 | + acg.despreadList(args, true); |
| 181 | + sig.append(getTypeDescription(Object[].class)); |
| 182 | + } else { |
| 183 | + for (Expression arg : args) { |
| 184 | + arg.visit(acg); |
| 185 | + if (arg instanceof CastExpression) { |
| 186 | + operandStack.box(); |
| 187 | + acg.loadWrapper(arg); |
| 188 | + sig.append(getTypeDescription(Wrapper.class)); |
| 189 | + } else { |
| 190 | + sig.append(getTypeDescription(operandStack.getTopOperand())); |
| 191 | + } |
| 192 | + nArgs++; |
| 193 | + } |
| 194 | + } |
| 195 | + sig.append(")Ljava/lang/Object;"); |
| 196 | + |
| 197 | + int flags = safe ? SAFE_NAVIGATION : 0; |
| 198 | + if (spread) flags |= SPREAD_CALL; |
| 199 | + controller.getMethodVisitor().visitInvokeDynamicInsn(METHOD.getCallSiteName(), sig.toString(), BSM, getMethodName(call.getMethod()), flags); |
| 200 | + operandStack.replace(OBJECT_TYPE, nArgs); |
| 201 | + if (end != null) controller.getMethodVisitor().visitLabel(end); |
| 202 | + } |
| 203 | + |
122 | 204 | private void finishIndyCall(final Handle bsmHandle, final String methodName, final String sig, final int numberOfArguments, final Object... bsmArgs) { |
123 | 205 | CompileStack compileStack = controller.getCompileStack(); |
124 | 206 | OperandStack operandStack = controller.getOperandStack(); |
|
0 commit comments