http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java new file mode 100644 index 0000000..1e434cb --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java @@ -0,0 +1,764 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.ASTNode; +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.GroovyCodeVisitor; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.decompiled.DecompiledClassNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.ArrayExpression; +import org.codehaus.groovy.ast.expr.AttributeExpression; +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.ExpressionTransformer; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.classgen.Verifier; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.CallSiteWriter; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.ExpressionAsVariableSlot; +import org.codehaus.groovy.classgen.asm.InvocationWriter; +import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.classgen.asm.VariableSlotLoader; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.codehaus.groovy.syntax.SyntaxException; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.sc.StaticCompilationVisitor; +import org.codehaus.groovy.transform.sc.TemporaryVariableExpression; +import org.codehaus.groovy.transform.stc.ExtensionMethodNode; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.getWrapper; +import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS; +import static org.objectweb.asm.Opcodes.ACONST_NULL; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.IFNULL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +public class StaticInvocationWriter extends InvocationWriter { + private static final ClassNode INVOKERHELPER_CLASSNODE = ClassHelper.make(InvokerHelper.class); + private static final Expression INVOKERHELER_RECEIVER = new ClassExpression(INVOKERHELPER_CLASSNODE); + private static final MethodNode INVOKERHELPER_INVOKEMETHOD = INVOKERHELPER_CLASSNODE.getMethod( + "invokeMethodSafe", + new Parameter[]{ + new Parameter(ClassHelper.OBJECT_TYPE, "object"), + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "args") + } + ); + + private static final MethodNode INVOKERHELPER_INVOKESTATICMETHOD = INVOKERHELPER_CLASSNODE.getMethod( + "invokeStaticMethod", + new Parameter[]{ + new Parameter(ClassHelper.CLASS_Type, "clazz"), + new Parameter(ClassHelper.STRING_TYPE, "name"), + new Parameter(ClassHelper.OBJECT_TYPE, "args") + } + ); + + private final AtomicInteger labelCounter = new AtomicInteger(); + + final WriterController controller; + + private MethodCallExpression currentCall; + + public StaticInvocationWriter(WriterController wc) { + super(wc); + controller = wc; + } + + @Override + protected boolean makeDirectCall(final Expression origin, final Expression receiver, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, final boolean implicitThis, final boolean containsSpreadExpression) { + if (origin instanceof MethodCallExpression && + receiver instanceof VariableExpression && + ((VariableExpression) receiver).isSuperExpression()) { + ClassNode superClass = receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); + if (superClass!=null && !controller.getCompileStack().isLHS()) { + // GROOVY-7300 + MethodCallExpression mce = (MethodCallExpression) origin; + MethodNode node = superClass.getDeclaredMethod(mce.getMethodAsString(), Parameter.EMPTY_ARRAY); + mce.setMethodTarget(node); + } + } + return super.makeDirectCall(origin, receiver, message, arguments, adapter, implicitThis, containsSpreadExpression); + } + + @Override + public void writeInvokeMethod(final MethodCallExpression call) { + MethodCallExpression old = currentCall; + currentCall = call; + super.writeInvokeMethod(call); + currentCall = old; + } + + @Override + public void writeInvokeConstructor(final ConstructorCallExpression call) { + MethodNode mn = call.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); + if (mn == null) { + super.writeInvokeConstructor(call); + return; + } + if (writeAICCall(call)) return; + ConstructorNode cn; + if (mn instanceof ConstructorNode) { + cn = (ConstructorNode) mn; + } else { + cn = new ConstructorNode(mn.getModifiers(), mn.getParameters(), mn.getExceptions(), mn.getCode()); + cn.setDeclaringClass(mn.getDeclaringClass()); + } + TupleExpression args = makeArgumentList(call.getArguments()); + if (cn.isPrivate()) { + ClassNode classNode = controller.getClassNode(); + ClassNode declaringClass = cn.getDeclaringClass(); + if (declaringClass != classNode) { + MethodNode bridge = null; + if (call.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS) != null) { + Map<MethodNode, MethodNode> bridgeMethods = declaringClass.getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS); + bridge = bridgeMethods != null ? bridgeMethods.get(cn) : null; + } + if (bridge != null && bridge instanceof ConstructorNode) { + ArgumentListExpression newArgs = new ArgumentListExpression(new ConstantExpression(null)); + for (Expression arg: args) { + newArgs.addExpression(arg); + } + cn = (ConstructorNode) bridge; + args = newArgs; + } else { + controller.getSourceUnit().addError(new SyntaxException("Cannot call private constructor for " + declaringClass.toString(false) + + " from class " + classNode.toString(false), call.getLineNumber(), call.getColumnNumber(), mn.getLastLineNumber(), call.getLastColumnNumber())); + } + } + } + + String ownerDescriptor = prepareConstructorCall(cn); + int before = controller.getOperandStack().getStackLength(); + loadArguments(args.getExpressions(), cn.getParameters()); + finnishConstructorCall(cn, ownerDescriptor, controller.getOperandStack().getStackLength() - before); + } + + @Override + public void writeSpecialConstructorCall(final ConstructorCallExpression call) { + MethodNode mn = call.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); + if (mn==null) { + super.writeSpecialConstructorCall(call); + return; + } + controller.getCompileStack().pushInSpecialConstructorCall(); + ConstructorNode cn; + if (mn instanceof ConstructorNode) { + cn = (ConstructorNode) mn; + } else { + cn = new ConstructorNode(mn.getModifiers(), mn.getParameters(), mn.getExceptions(), mn.getCode()); + cn.setDeclaringClass(mn.getDeclaringClass()); + } + // load "this" + controller.getMethodVisitor().visitVarInsn(ALOAD, 0); + String ownerDescriptor = BytecodeHelper.getClassInternalName(cn.getDeclaringClass()); + TupleExpression args = makeArgumentList(call.getArguments()); + int before = controller.getOperandStack().getStackLength(); + loadArguments(args.getExpressions(), cn.getParameters()); + finnishConstructorCall(cn, ownerDescriptor, controller.getOperandStack().getStackLength() - before); + // on a special call, there's no object on stack + controller.getOperandStack().remove(1); + controller.getCompileStack().pop(); + } + + /** + * Attempts to make a direct method call on a bridge method, if it exists. + */ + @Deprecated + protected boolean tryBridgeMethod(MethodNode target, Expression receiver, boolean implicitThis, TupleExpression args) { + return tryBridgeMethod(target, receiver, implicitThis, args, null); + } + + /** + * Attempts to make a direct method call on a bridge method, if it exists. + */ + protected boolean tryBridgeMethod(MethodNode target, Expression receiver, boolean implicitThis, + TupleExpression args, ClassNode thisClass) { + ClassNode lookupClassNode; + if (target.isProtected()) { + lookupClassNode = controller.getClassNode(); + while (lookupClassNode != null && !lookupClassNode.isDerivedFrom(target.getDeclaringClass())) { + lookupClassNode = lookupClassNode.getOuterClass(); + } + if (lookupClassNode == null) { + return false; + } + } else { + lookupClassNode = target.getDeclaringClass().redirect(); + } + Map<MethodNode, MethodNode> bridges = lookupClassNode.getNodeMetaData(PRIVATE_BRIDGE_METHODS); + MethodNode bridge = bridges==null?null:bridges.get(target); + if (bridge != null) { + Expression fixedReceiver = receiver; + if (implicitThis) { + if (!controller.isInClosure()) { + fixedReceiver = new PropertyExpression(new ClassExpression(lookupClassNode), "this"); + } else if (thisClass != null) { + ClassNode current = thisClass.getOuterClass(); + fixedReceiver = new VariableExpression("thisObject", current); + // adjust for multiple levels of nesting if needed + while (current != null && current instanceof InnerClassNode && !lookupClassNode.equals(current)) { + FieldNode thisField = current.getField("this$0"); + current = current.getOuterClass(); + if (thisField != null) { + fixedReceiver = new PropertyExpression(fixedReceiver, "this$0"); + fixedReceiver.setType(current); + } + } + } + } + ArgumentListExpression newArgs = new ArgumentListExpression(target.isStatic()?new ConstantExpression(null):fixedReceiver); + for (Expression expression : args.getExpressions()) { + newArgs.addExpression(expression); + } + return writeDirectMethodCall(bridge, implicitThis, fixedReceiver, newArgs); + } + return false; + } + + @Override + protected boolean writeDirectMethodCall(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args) { + if (target==null) return false; + + if (target instanceof ExtensionMethodNode) { + ExtensionMethodNode emn = (ExtensionMethodNode) target; + MethodNode node = emn.getExtensionMethodNode(); + String methodName = target.getName(); + + MethodVisitor mv = controller.getMethodVisitor(); + int argumentsToRemove = 0; + List<Expression> argumentList = new LinkedList<Expression>(args.getExpressions()); + + if (emn.isStaticExtension()) { + // it's a static extension method + argumentList.add(0, ConstantExpression.NULL); + } else { + argumentList.add(0, receiver); + } + + Parameter[] parameters = node.getParameters(); + loadArguments(argumentList, parameters); + + String owner = BytecodeHelper.getClassInternalName(node.getDeclaringClass()); + String desc = BytecodeHelper.getMethodDescriptor(target.getReturnType(), parameters); + mv.visitMethodInsn(INVOKESTATIC, owner, methodName, desc, false); + ClassNode ret = target.getReturnType().redirect(); + if (ret == ClassHelper.VOID_TYPE) { + ret = ClassHelper.OBJECT_TYPE; + mv.visitInsn(ACONST_NULL); + } + argumentsToRemove += argumentList.size(); + controller.getOperandStack().remove(argumentsToRemove); + controller.getOperandStack().push(ret); + return true; + } else { + if (target == StaticTypeCheckingVisitor.CLOSURE_CALL_VARGS) { + // wrap arguments into an array + ArrayExpression arr = new ArrayExpression(ClassHelper.OBJECT_TYPE, args.getExpressions()); + return super.writeDirectMethodCall(target, implicitThis, receiver, new ArgumentListExpression(arr)); + } + ClassNode classNode = controller.getClassNode(); + if (classNode.isDerivedFrom(ClassHelper.CLOSURE_TYPE) + && controller.isInClosure() + && !target.isPublic() + && target.getDeclaringClass() != classNode) { + if (!tryBridgeMethod(target, receiver, implicitThis, args, classNode)) { + // replace call with an invoker helper call + ArrayExpression arr = new ArrayExpression(ClassHelper.OBJECT_TYPE, args.getExpressions()); + MethodCallExpression mce = new MethodCallExpression( + INVOKERHELER_RECEIVER, + target.isStatic() ? "invokeStaticMethod" : "invokeMethodSafe", + new ArgumentListExpression( + target.isStatic() ? + new ClassExpression(target.getDeclaringClass()) : + receiver, + new ConstantExpression(target.getName()), + arr + ) + ); + mce.setMethodTarget(target.isStatic() ? INVOKERHELPER_INVOKESTATICMETHOD : INVOKERHELPER_INVOKEMETHOD); + mce.visit(controller.getAcg()); + return true; + } + return true; + } + Expression fixedReceiver = null; + boolean fixedImplicitThis = implicitThis; + if (target.isPrivate()) { + if (tryPrivateMethod(target, implicitThis, receiver, args, classNode)) return true; + } else if (target.isProtected()) { + ClassNode node = receiver==null?ClassHelper.OBJECT_TYPE:controller.getTypeChooser().resolveType(receiver, controller.getClassNode()); + boolean isThisOrSuper = false; + if (receiver instanceof VariableExpression) { + isThisOrSuper = ((VariableExpression) receiver).isThisExpression() || ((VariableExpression) receiver).isSuperExpression(); + } + if (!implicitThis && !isThisOrSuper + && StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(node,target.getDeclaringClass())) { + ASTNode src = receiver==null?args:receiver; + controller.getSourceUnit().addError( + new SyntaxException("Method " + target.getName() + " is protected in " + target.getDeclaringClass().toString(false), + src.getLineNumber(), src.getColumnNumber(), src.getLastLineNumber(), src.getLastColumnNumber())); + } else if (!node.isDerivedFrom(target.getDeclaringClass()) && tryBridgeMethod(target, receiver, implicitThis, args, classNode)) { + return true; + } + } else if (target.isPublic() && receiver != null) { + if (implicitThis + && !classNode.isDerivedFrom(target.getDeclaringClass()) + && !classNode.implementsInterface(target.getDeclaringClass()) + && classNode instanceof InnerClassNode && controller.isInClosure()) { + ClassNode current = classNode.getOuterClass(); + fixedReceiver = new VariableExpression("thisObject", current); + // adjust for multiple levels of nesting if needed + while (current != null && current instanceof InnerClassNode && !classNode.equals(current)) { + FieldNode thisField = current.getField("this$0"); + current = current.getOuterClass(); + if (thisField != null) { + fixedReceiver = new PropertyExpression(fixedReceiver, "this$0"); + fixedReceiver.setType(current); + fixedImplicitThis = false; + } + } + } + } + if (receiver != null) { + boolean callToSuper = receiver instanceof VariableExpression && ((VariableExpression) receiver).isSuperExpression(); + if (!callToSuper) { + fixedReceiver = fixedReceiver == null ? receiver : fixedReceiver; + // in order to avoid calls to castToType, which is the dynamic behaviour, we make sure that we call CHECKCAST instead + // then replace the top operand type + Expression checkCastReceiver = new CheckcastReceiverExpression(fixedReceiver, target); + return super.writeDirectMethodCall(target, fixedImplicitThis, checkCastReceiver, args); + } + } + return super.writeDirectMethodCall(target, implicitThis, receiver, args); + } + } + + private boolean tryPrivateMethod(final MethodNode target, final boolean implicitThis, final Expression receiver, final TupleExpression args, final ClassNode classNode) { + ClassNode declaringClass = target.getDeclaringClass(); + if ((isPrivateBridgeMethodsCallAllowed(declaringClass, classNode) || isPrivateBridgeMethodsCallAllowed(classNode, declaringClass)) + && declaringClass.getNodeMetaData(PRIVATE_BRIDGE_METHODS) != null + && !declaringClass.equals(classNode)) { + if (tryBridgeMethod(target, receiver, implicitThis, args, classNode)) { + return true; + } else if (declaringClass != classNode) { + controller.getSourceUnit().addError(new SyntaxException("Cannot call private method " + (target.isStatic() ? "static " : "") + + declaringClass.toString(false) + "#" + target.getName() + " from class " + classNode.toString(false), receiver.getLineNumber(), receiver.getColumnNumber(), receiver.getLastLineNumber(), receiver.getLastColumnNumber())); + } + } + if (declaringClass != classNode) { + controller.getSourceUnit().addError(new SyntaxException("Cannot call private method " + (target.isStatic() ? "static " : "") + + declaringClass.toString(false) + "#" + target.getName() + " from class " + classNode.toString(false), receiver.getLineNumber(), receiver.getColumnNumber(), receiver.getLastLineNumber(), receiver.getLastColumnNumber())); + } + return false; + } + + protected static boolean isPrivateBridgeMethodsCallAllowed(ClassNode receiver, ClassNode caller) { + if (receiver == null) return false; + if (receiver.redirect() == caller) return true; + if (caller.redirect() instanceof InnerClassNode) return + isPrivateBridgeMethodsCallAllowed(receiver, caller.redirect().getOuterClass()) || + isPrivateBridgeMethodsCallAllowed(receiver.getOuterClass(), caller); + return false; + } + + protected void loadArguments(List<Expression> argumentList, Parameter[] para) { + if (para.length == 0) return; + ClassNode lastParaType = para[para.length - 1].getOriginType(); + AsmClassGenerator acg = controller.getAcg(); + TypeChooser typeChooser = controller.getTypeChooser(); + OperandStack operandStack = controller.getOperandStack(); + ClassNode lastArgType = !argumentList.isEmpty() ? + typeChooser.resolveType(argumentList.get(argumentList.size()-1), controller.getClassNode()):null; + if (lastParaType.isArray() + && ((argumentList.size() > para.length) + || ((argumentList.size() == (para.length - 1)) && !lastParaType.equals(lastArgType)) + || ((argumentList.size() == para.length && lastArgType!=null && !lastArgType.isArray()) + && (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(lastArgType,lastParaType.getComponentType()))) + || ClassHelper.GSTRING_TYPE.equals(lastArgType) && ClassHelper.STRING_TYPE.equals(lastParaType.getComponentType())) + ) { + int stackLen = operandStack.getStackLength() + argumentList.size(); + MethodVisitor mv = controller.getMethodVisitor(); + //mv = new org.objectweb.asm.util.TraceMethodVisitor(mv); + controller.setMethodVisitor(mv); + // varg call + // first parameters as usual + for (int i = 0; i < para.length - 1; i++) { + Expression expression = argumentList.get(i); + expression.visit(acg); + if (!isNullConstant(expression)) { + operandStack.doGroovyCast(para[i].getType()); + } + } + // last parameters wrapped in an array + List<Expression> lastParams = new LinkedList<Expression>(); + for (int i = para.length - 1; i < argumentList.size(); i++) { + 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 if (argumentList.size() == para.length) { + for (int i = 0; i < argumentList.size(); i++) { + Expression expression = argumentList.get(i); + expression.visit(acg); + if (!isNullConstant(expression)) { + operandStack.doGroovyCast(para[i].getType()); + } + } + } else { + // method call with default arguments + ClassNode classNode = controller.getClassNode(); + Expression[] arguments = new Expression[para.length]; + for (int i = 0, j = 0; i < para.length; i++) { + Parameter curParam = para[i]; + ClassNode curParamType = curParam.getType(); + Expression curArg = j < argumentList.size() ? argumentList.get(j) : null; + Expression initialExpression = curParam.getNodeMetaData(StaticTypesMarker.INITIAL_EXPRESSION); + if (initialExpression == null && curParam.hasInitialExpression()) + initialExpression = curParam.getInitialExpression(); + if (initialExpression == null && curParam.getNodeMetaData(Verifier.INITIAL_EXPRESSION)!=null) { + initialExpression = curParam.getNodeMetaData(Verifier.INITIAL_EXPRESSION); + } + ClassNode curArgType = curArg == null ? null : typeChooser.resolveType(curArg, classNode); + + if (initialExpression != null && !compatibleArgumentType(curArgType, curParamType)) { + // use default expression + arguments[i] = initialExpression; + } else { + arguments[i] = curArg; + j++; + } + } + for (int i = 0; i < arguments.length; i++) { + Expression expression = arguments[i]; + expression.visit(acg); + if (!isNullConstant(expression)) { + operandStack.doGroovyCast(para[i].getType()); + } + } + } + } + + private static boolean isNullConstant(final Expression expression) { + return (expression instanceof ConstantExpression && ((ConstantExpression) expression).getValue() == null); + } + + private boolean compatibleArgumentType(ClassNode argumentType, ClassNode paramType) { + if (argumentType == null) return false; + if (ClassHelper.getWrapper(argumentType).equals(ClassHelper.getWrapper(paramType))) return true; + if (paramType.isInterface()) return argumentType.implementsInterface(paramType); + if (paramType.isArray() && argumentType.isArray()) + return compatibleArgumentType(argumentType.getComponentType(), paramType.getComponentType()); + return ClassHelper.getWrapper(argumentType).isDerivedFrom(ClassHelper.getWrapper(paramType)); + } + + @Override + public void makeCall(final Expression origin, final Expression receiver, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, final boolean safe, final boolean spreadSafe, final boolean implicitThis) { + ClassNode dynamicCallReturnType = origin.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); + if (dynamicCallReturnType !=null) { + StaticTypesWriterController staticController = (StaticTypesWriterController) controller; + if (origin instanceof MethodCallExpression) { + ((MethodCallExpression) origin).setMethodTarget(null); + } + InvocationWriter dynamicInvocationWriter = staticController.getRegularInvocationWriter(); + dynamicInvocationWriter. + makeCall(origin, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); + return; + } + if (tryImplicitReceiver(origin, message, arguments, adapter, safe, spreadSafe, implicitThis)) { + return; + } + // if call is spread safe, replace it with a for in loop + if (spreadSafe && origin instanceof MethodCallExpression) { + // receiver expressions with side effects should not be visited twice, avoid by using a temporary variable + Expression tmpReceiver = receiver; + if (!(receiver instanceof VariableExpression) && !(receiver instanceof ConstantExpression)) { + tmpReceiver = new TemporaryVariableExpression(receiver); + } + MethodVisitor mv = controller.getMethodVisitor(); + CompileStack compileStack = controller.getCompileStack(); + TypeChooser typeChooser = controller.getTypeChooser(); + OperandStack operandStack = controller.getOperandStack(); + ClassNode classNode = controller.getClassNode(); + int counter = labelCounter.incrementAndGet(); + + // use a temporary variable for the arraylist in which the results of the spread call will be stored + ConstructorCallExpression cce = new ConstructorCallExpression(StaticCompilationVisitor.ARRAYLIST_CLASSNODE, ArgumentListExpression.EMPTY_ARGUMENTS); + cce.setNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, StaticCompilationVisitor.ARRAYLIST_CONSTRUCTOR); + TemporaryVariableExpression result = new TemporaryVariableExpression(cce); + result.visit(controller.getAcg()); + operandStack.pop(); + // if (receiver != null) + tmpReceiver.visit(controller.getAcg()); + Label ifnull = compileStack.createLocalLabel("ifnull_" + counter); + mv.visitJumpInsn(IFNULL, ifnull); + operandStack.remove(1); // receiver consumed by if() + Label nonull = compileStack.createLocalLabel("nonull_" + counter); + mv.visitLabel(nonull); + ClassNode componentType = StaticTypeCheckingVisitor.inferLoopElementType(typeChooser.resolveType(tmpReceiver, classNode)); + Parameter iterator = new Parameter(componentType, "for$it$" + counter); + VariableExpression iteratorAsVar = new VariableExpression(iterator); + MethodCallExpression origMCE = (MethodCallExpression) origin; + MethodCallExpression newMCE = new MethodCallExpression( + iteratorAsVar, + origMCE.getMethodAsString(), + origMCE.getArguments() + ); + newMCE.setImplicitThis(false); + newMCE.setMethodTarget(origMCE.getMethodTarget()); + newMCE.setSafe(true); + MethodCallExpression add = new MethodCallExpression( + result, + "add", + newMCE + ); + add.setImplicitThis(false); + add.setMethodTarget(StaticCompilationVisitor.ARRAYLIST_ADD_METHOD); + // for (e in receiver) { result.add(e?.method(arguments) } + ForStatement stmt = new ForStatement( + iterator, + tmpReceiver, + new ExpressionStatement(add) + ); + stmt.visit(controller.getAcg()); + // else { empty list } + mv.visitLabel(ifnull); + + // end of if/else + // return result list + result.visit(controller.getAcg()); + + // cleanup temporary variables + if (tmpReceiver instanceof TemporaryVariableExpression) { + ((TemporaryVariableExpression) tmpReceiver).remove(controller); + } + result.remove(controller); + } else if (safe && origin instanceof MethodCallExpression) { + // wrap call in an IFNULL check + MethodVisitor mv = controller.getMethodVisitor(); + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + int counter = labelCounter.incrementAndGet(); + // if (receiver != null) + ExpressionAsVariableSlot slot = new ExpressionAsVariableSlot(controller, receiver); + slot.visit(controller.getAcg()); + operandStack.box(); + Label ifnull = compileStack.createLocalLabel("ifnull_" + counter); + mv.visitJumpInsn(IFNULL, ifnull); + operandStack.remove(1); // receiver consumed by if() + Label nonull = compileStack.createLocalLabel("nonull_" + counter); + mv.visitLabel(nonull); + MethodCallExpression origMCE = (MethodCallExpression) origin; + MethodCallExpression newMCE = new MethodCallExpression( + new VariableSlotLoader(slot.getType(), slot.getIndex(), controller.getOperandStack()), + origMCE.getMethodAsString(), + origMCE.getArguments() + ); + MethodNode methodTarget = origMCE.getMethodTarget(); + newMCE.setMethodTarget(methodTarget); + newMCE.setSafe(false); + newMCE.setImplicitThis(origMCE.isImplicitThis()); + newMCE.setSourcePosition(origMCE); + newMCE.visit(controller.getAcg()); + compileStack.removeVar(slot.getIndex()); + ClassNode returnType = operandStack.getTopOperand(); + if (ClassHelper.isPrimitiveType(returnType) && !ClassHelper.VOID_TYPE.equals(returnType)) { + operandStack.box(); + } + Label endof = compileStack.createLocalLabel("endof_" + counter); + mv.visitJumpInsn(GOTO, endof); + mv.visitLabel(ifnull); + // else { null } + mv.visitInsn(ACONST_NULL); + mv.visitLabel(endof); + } else { + if ((adapter == AsmClassGenerator.getGroovyObjectField + || adapter == AsmClassGenerator.getField ) && origin instanceof AttributeExpression) { + String pname = ((PropertyExpression) origin).getPropertyAsString(); + CallSiteWriter callSiteWriter = controller.getCallSiteWriter(); + if (pname!=null && callSiteWriter instanceof StaticTypesCallSiteWriter) { + StaticTypesCallSiteWriter stcsw = (StaticTypesCallSiteWriter) callSiteWriter; + TypeChooser typeChooser = controller.getTypeChooser(); + if (stcsw.makeGetField(receiver, typeChooser.resolveType(receiver, controller.getClassNode()), pname, safe, false, true)) { + return; + } + } + } + super.makeCall(origin, receiver, message, arguments, adapter, safe, spreadSafe, implicitThis); + } + } + + boolean tryImplicitReceiver(final Expression origin, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, final boolean safe, final boolean spreadSafe, final boolean implicitThis) { + Object implicitReceiver = origin.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + if (implicitThis && implicitReceiver==null && origin instanceof MethodCallExpression) { + implicitReceiver = ((MethodCallExpression) origin).getObjectExpression().getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + } + if (implicitReceiver !=null && implicitThis) { + String[] propertyPath = ((String) implicitReceiver).split("\\."); + // GROOVY-6021 + PropertyExpression pexp = new PropertyExpression(new VariableExpression("this", CLOSURE_TYPE), propertyPath[0]); + pexp.setImplicitThis(true); + for (int i=1; i<propertyPath.length;i++) { + pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE); + pexp = new PropertyExpression(pexp, propertyPath[i]); + } + pexp.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, implicitReceiver); + origin.removeNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); + if (origin instanceof PropertyExpression) { + PropertyExpression rewritten = new PropertyExpression( + pexp, + ((PropertyExpression) origin).getProperty(), + ((PropertyExpression) origin).isSafe() + ); + rewritten.setSpreadSafe(((PropertyExpression) origin).isSpreadSafe()); + rewritten.setImplicitThis(false); + rewritten.visit(controller.getAcg()); + rewritten.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, origin.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE)); + return true; + } + makeCall(origin, pexp, message, arguments, adapter, safe, spreadSafe, false); + return true; + } + return false; + } + + private class CheckcastReceiverExpression extends Expression { + private final Expression receiver; + private final MethodNode target; + + private ClassNode resolvedType; + + public CheckcastReceiverExpression(final Expression receiver, final MethodNode target) { + this.receiver = receiver; + this.target = target; + } + + @Override + public Expression transformExpression(final ExpressionTransformer transformer) { + return this; + } + + @Override + public void visit(final GroovyCodeVisitor visitor) { + receiver.visit(visitor); + if (visitor instanceof AsmClassGenerator) { + ClassNode topOperand = controller.getOperandStack().getTopOperand(); + ClassNode type = getType(); + if (ClassHelper.GSTRING_TYPE.equals(topOperand) && ClassHelper.STRING_TYPE.equals(type)) { + // perform regular type conversion + controller.getOperandStack().doGroovyCast(type); + return; + } + if (ClassHelper.isPrimitiveType(topOperand) && !ClassHelper.isPrimitiveType(type)) { + controller.getOperandStack().box(); + } else if (!ClassHelper.isPrimitiveType(topOperand) && ClassHelper.isPrimitiveType(type)) { + controller.getOperandStack().doGroovyCast(type); + } + if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(topOperand, type)) return; + controller.getMethodVisitor().visitTypeInsn(CHECKCAST, type.isArray() ? + BytecodeHelper.getTypeDescription(type) : + BytecodeHelper.getClassInternalName(type.getName())); + controller.getOperandStack().replace(type); + } + } + + @Override + public ClassNode getType() { + if (resolvedType!=null) { + return resolvedType; + } + ClassNode type; + if (target instanceof ExtensionMethodNode) { + type = ((ExtensionMethodNode) target).getExtensionMethodNode().getDeclaringClass(); + } else { + type = getWrapper(controller.getTypeChooser().resolveType(receiver, controller.getClassNode())); + ClassNode declaringClass = target.getDeclaringClass(); + if (type.getClass() != ClassNode.class + && type.getClass() != InnerClassNode.class + && type.getClass() != DecompiledClassNode.class) { + type = declaringClass; // ex: LUB type + } + if (OBJECT_TYPE.equals(type) && !OBJECT_TYPE.equals(declaringClass)) { + // can happen for compiler rewritten code, where type information is missing + type = declaringClass; + } + if (OBJECT_TYPE.equals(declaringClass)) { + // check cast not necessary because Object never evolves + // and it prevents a potential ClassCastException if the delegate of a closure + // is changed in a statically compiled closure + type = OBJECT_TYPE; + } + } + resolvedType = type; + return type; + } + } + + public MethodCallExpression getCurrentCall() { + return currentCall; + } + + @Override + protected boolean makeCachedCall(Expression origin, ClassExpression sender, Expression receiver, Expression message, Expression arguments, MethodCallerMultiAdapter adapter, boolean safe, boolean spreadSafe, boolean implicitThis, boolean containsSpreadExpression) { + return false; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java new file mode 100644 index 0000000..6027a0c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticPropertyAccessHelper.java @@ -0,0 +1,133 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.GroovyCodeVisitor; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ExpressionTransformer; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.classgen.AsmClassGenerator; +import org.codehaus.groovy.transform.sc.ListOfExpressionsExpression; +import org.codehaus.groovy.transform.sc.TemporaryVariableExpression; + +import java.util.Arrays; + +/** + * Contains helper methods aimed at facilitating the generation of statically compiled bytecode for property access. + * + * @author Cédric Champeau + * @since 2.4.0 + */ +public abstract class StaticPropertyAccessHelper { + public static Expression transformToSetterCall( + Expression receiver, + MethodNode setterMethod, + final Expression arguments, + boolean implicitThis, + boolean safe, + boolean spreadSafe, + boolean requiresReturnValue, + Expression location) { + if (requiresReturnValue) { + TemporaryVariableExpression tmp = new TemporaryVariableExpression(arguments); + PoppingMethodCallExpression call = new PoppingMethodCallExpression(receiver, setterMethod, tmp); + call.setImplicitThis(implicitThis); + call.setSafe(safe); + call.setSpreadSafe(spreadSafe); + call.setSourcePosition(location); + PoppingListOfExpressionsExpression result = new PoppingListOfExpressionsExpression(tmp, call); + result.setSourcePosition(location); + return result; + } else { + MethodCallExpression call = new MethodCallExpression( + receiver, + setterMethod.getName(), + arguments + ); + call.setImplicitThis(implicitThis); + call.setSafe(safe); + call.setSpreadSafe(spreadSafe); + call.setMethodTarget(setterMethod); + call.setSourcePosition(location); + return call; + } + } + + private static class PoppingListOfExpressionsExpression extends ListOfExpressionsExpression { + private final TemporaryVariableExpression tmp; + private final PoppingMethodCallExpression call; + + public PoppingListOfExpressionsExpression(final TemporaryVariableExpression tmp, final PoppingMethodCallExpression call) { + super(Arrays.asList( + tmp, + call + )); + this.tmp = tmp; + this.call = call; + } + + @Override + public Expression transformExpression(final ExpressionTransformer transformer) { + PoppingMethodCallExpression tcall = (PoppingMethodCallExpression) call.transformExpression(transformer); + return new PoppingListOfExpressionsExpression(tcall.tmp, tcall); + } + + @Override + public void visit(final GroovyCodeVisitor visitor) { + super.visit(visitor); + if (visitor instanceof AsmClassGenerator) { + tmp.remove(((AsmClassGenerator) visitor).getController()); + } + } + } + + private static class PoppingMethodCallExpression extends MethodCallExpression { + private final Expression receiver; + private final MethodNode setter; + private final TemporaryVariableExpression tmp; + + public PoppingMethodCallExpression(final Expression receiver, final MethodNode setterMethod, final TemporaryVariableExpression tmp) { + super(receiver, setterMethod.getName(), tmp); + this.receiver = receiver; + this.setter = setterMethod; + this.tmp = tmp; + setMethodTarget(setterMethod); + } + + @Override + public Expression transformExpression(final ExpressionTransformer transformer) { + PoppingMethodCallExpression trn = new PoppingMethodCallExpression(receiver.transformExpression(transformer), setter, (TemporaryVariableExpression) tmp.transformExpression(transformer)); + trn.copyNodeMetaData(this); + trn.setImplicitThis(isImplicitThis()); + trn.setSafe(isSafe()); + trn.setSpreadSafe(isSpreadSafe()); + return trn; + } + + @Override + public void visit(final GroovyCodeVisitor visitor) { + super.visit(visitor); + if (visitor instanceof AsmClassGenerator) { + // ignore the return of the call + ((AsmClassGenerator) visitor).getController().getOperandStack().pop(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java new file mode 100644 index 0000000..116fd16 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java @@ -0,0 +1,426 @@ +/* + * 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.sc; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.AttributeExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +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.DeclarationExpression; +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.VariableExpression; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.tools.WideningCategories; +import org.codehaus.groovy.classgen.asm.BinaryExpressionMultiTypeDispatcher; +import org.codehaus.groovy.classgen.asm.BinaryExpressionWriter; +import org.codehaus.groovy.classgen.asm.BytecodeHelper; +import org.codehaus.groovy.classgen.asm.CompileStack; +import org.codehaus.groovy.classgen.asm.OperandStack; +import org.codehaus.groovy.classgen.asm.TypeChooser; +import org.codehaus.groovy.classgen.asm.VariableSlotLoader; +import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.syntax.Token; +import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys; +import org.codehaus.groovy.transform.sc.StaticCompilationVisitor; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; +import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_ADD_METHOD; +import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_CLASSNODE; +import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.ARRAYLIST_CONSTRUCTOR; + +/** + * A specialized version of the multi type binary expression dispatcher which is aware of static compilation. + * It is able to generate optimized bytecode for some operations using JVM instructions when available. + * + * @author Cedric Champeau + * @author Jochen Theodorou + */ +public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpressionMultiTypeDispatcher implements Opcodes { + + private final AtomicInteger labelCounter = new AtomicInteger(); + private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", new Parameter[0]); + + + public StaticTypesBinaryExpressionMultiTypeDispatcher(WriterController wc) { + super(wc); + } + + @Override + protected void writePostOrPrefixMethod(int op, String method, Expression expression, Expression orig) { + MethodNode mn = orig.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); + WriterController controller = getController(); + OperandStack operandStack = controller.getOperandStack(); + if (mn!=null) { + operandStack.pop(); + MethodCallExpression call = new MethodCallExpression( + expression, + method, + ArgumentListExpression.EMPTY_ARGUMENTS + ); + call.setMethodTarget(mn); + call.visit(controller.getAcg()); + return; + } + + ClassNode top = operandStack.getTopOperand(); + if (ClassHelper.isPrimitiveType(top) && (ClassHelper.isNumberType(top)||char_TYPE.equals(top))) { + MethodVisitor mv = controller.getMethodVisitor(); + if (WideningCategories.isIntCategory(top) || char_TYPE.equals(top)) { + mv.visitInsn(ICONST_1); + } else if (long_TYPE.equals(top)) { + mv.visitInsn(LCONST_1); + } else if (float_TYPE.equals(top)) { + mv.visitInsn(FCONST_1); + } else if (double_TYPE.equals(top)) { + mv.visitInsn(DCONST_1); + } + if ("next".equals(method)) { + if (WideningCategories.isIntCategory(top) || char_TYPE.equals(top)) { + mv.visitInsn(IADD); + } else if (long_TYPE.equals(top)) { + mv.visitInsn(LADD); + } else if (float_TYPE.equals(top)) { + mv.visitInsn(FADD); + } else if (double_TYPE.equals(top)) { + mv.visitInsn(DADD); + } + } else { + if (WideningCategories.isIntCategory(top) || char_TYPE.equals(top)) { + mv.visitInsn(ISUB); + } else if (long_TYPE.equals(top)) { + mv.visitInsn(LSUB); + } else if (float_TYPE.equals(top)) { + mv.visitInsn(FSUB); + } else if (double_TYPE.equals(top)) { + mv.visitInsn(DSUB); + } + } + return; + } + super.writePostOrPrefixMethod(op, method, expression, orig); + } + + @Override + public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) { + if (!defineVariable) { + Expression leftExpression = expression.getLeftExpression(); + if (leftExpression instanceof PropertyExpression) { + PropertyExpression pexp = (PropertyExpression) leftExpression; + if (makeSetProperty( + pexp.getObjectExpression(), + pexp.getProperty(), + expression.getRightExpression(), + pexp.isSafe(), + pexp.isSpreadSafe(), + pexp.isImplicitThis(), + pexp instanceof AttributeExpression)) return; + } + } + // GROOVY-5620: Spread safe/Null safe operator on LHS is not supported + if (expression.getLeftExpression() instanceof PropertyExpression + && ((PropertyExpression) expression.getLeftExpression()).isSpreadSafe() + && StaticTypeCheckingSupport.isAssignment(expression.getOperation().getType())) { + // rewrite it so that it can be statically compiled + transformSpreadOnLHS(expression); + return; + } + super.evaluateEqual(expression, defineVariable); + } + + private void transformSpreadOnLHS(BinaryExpression origin) { + PropertyExpression spreadExpression = (PropertyExpression) origin.getLeftExpression(); + Expression value = origin.getRightExpression(); + WriterController controller = getController(); + MethodVisitor mv = controller.getMethodVisitor(); + CompileStack compileStack = controller.getCompileStack(); + TypeChooser typeChooser = controller.getTypeChooser(); + OperandStack operandStack = controller.getOperandStack(); + ClassNode classNode = controller.getClassNode(); + int counter = labelCounter.incrementAndGet(); + Expression receiver = spreadExpression.getObjectExpression(); + + // create an empty arraylist + VariableExpression result = new VariableExpression( + this.getClass().getSimpleName()+"$spreadresult" + counter, + ARRAYLIST_CLASSNODE + ); + ConstructorCallExpression cce = new ConstructorCallExpression(ARRAYLIST_CLASSNODE, ArgumentListExpression.EMPTY_ARGUMENTS); + cce.setNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, ARRAYLIST_CONSTRUCTOR); + DeclarationExpression declr = new DeclarationExpression( + result, + Token.newSymbol("=", spreadExpression.getLineNumber(), spreadExpression.getColumnNumber()), + cce + ); + declr.visit(controller.getAcg()); + // if (receiver != null) + receiver.visit(controller.getAcg()); + Label ifnull = compileStack.createLocalLabel("ifnull_" + counter); + mv.visitJumpInsn(IFNULL, ifnull); + operandStack.remove(1); // receiver consumed by if() + Label nonull = compileStack.createLocalLabel("nonull_" + counter); + mv.visitLabel(nonull); + ClassNode componentType = StaticTypeCheckingVisitor.inferLoopElementType(typeChooser.resolveType(receiver, classNode)); + Parameter iterator = new Parameter(componentType, "for$it$" + counter); + VariableExpression iteratorAsVar = new VariableExpression(iterator); + PropertyExpression pexp = spreadExpression instanceof AttributeExpression ? + new AttributeExpression(iteratorAsVar, spreadExpression.getProperty(), true): + new PropertyExpression(iteratorAsVar, spreadExpression.getProperty(), true); + pexp.setImplicitThis(spreadExpression.isImplicitThis()); + pexp.setSourcePosition(spreadExpression); + BinaryExpression assignment = new BinaryExpression( + pexp, + origin.getOperation(), + value + ); + MethodCallExpression add = new MethodCallExpression( + result, + "add", + assignment + ); + add.setMethodTarget(ARRAYLIST_ADD_METHOD); + // for (e in receiver) { result.add(e?.method(arguments) } + ForStatement stmt = new ForStatement( + iterator, + receiver, + new ExpressionStatement(add) + ); + stmt.visit(controller.getAcg()); + // else { empty list } + mv.visitLabel(ifnull); + + // end of if/else + // return result list + result.visit(controller.getAcg()); + + } + + private boolean makeSetProperty(final Expression receiver, final Expression message, final Expression arguments, final boolean safe, final boolean spreadSafe, final boolean implicitThis, final boolean isAttribute) { + WriterController controller = getController(); + TypeChooser typeChooser = controller.getTypeChooser(); + ClassNode receiverType = typeChooser.resolveType(receiver, controller.getClassNode()); + String property = message.getText(); + boolean isThisExpression = receiver instanceof VariableExpression && ((VariableExpression) receiver).isThisExpression(); + if (isAttribute + || (isThisExpression && + receiverType.getDeclaredField(property)!=null)) { + ClassNode current = receiverType; + FieldNode fn = null; + while (fn==null && current!=null) { + fn = current.getDeclaredField(property); + if (fn==null){ + current = current.getSuperClass(); + } + } + if (fn!=null && receiverType!=current && !fn.isPublic()) { + // check that direct access is allowed + if (!fn.isProtected()) { + return false; + } + String pkg1 = receiverType.getPackageName(); + String pkg2 = current.getPackageName(); + if (pkg1!=pkg2 && !pkg1.equals(pkg2)) { + return false; + } + OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); + if (!fn.isStatic()) { + receiver.visit(controller.getAcg()); + } + arguments.visit(controller.getAcg()); + operandStack.doGroovyCast(fn.getOriginType()); + mv.visitFieldInsn(fn.isStatic() ? PUTSTATIC : PUTFIELD, + BytecodeHelper.getClassInternalName(fn.getOwner()), + property, + BytecodeHelper.getTypeDescription(fn.getOriginType())); + operandStack.remove(fn.isStatic()?1:2); + return true; + } + } + if (!isAttribute) { + String setter = "set" + MetaClassHelper.capitalize(property); + MethodNode setterMethod = receiverType.getSetterMethod(setter, false); + ClassNode declaringClass = setterMethod!=null?setterMethod.getDeclaringClass():null; + if (isThisExpression && declaringClass!=null && declaringClass.equals(controller.getClassNode())) { + // this.x = ... shouldn't use a setter if in the same class + setterMethod = null; + } else if (setterMethod == null) { + PropertyNode propertyNode = receiverType.getProperty(property); + if (propertyNode != null) { + int mods = propertyNode.getModifiers(); + if (!Modifier.isFinal(mods)) { + setterMethod = new MethodNode( + setter, + ACC_PUBLIC, + ClassHelper.VOID_TYPE, + new Parameter[]{new Parameter(propertyNode.getOriginType(), "value")}, + ClassNode.EMPTY_ARRAY, + EmptyStatement.INSTANCE + ); + setterMethod.setDeclaringClass(receiverType); + } + } + } + if (setterMethod != null) { + Expression call = StaticPropertyAccessHelper.transformToSetterCall( + receiver, + setterMethod, + arguments, + implicitThis, + safe, + spreadSafe, + true, // to be replaced with a proper test whether a return value should be used or not + message + ); + call.visit(controller.getAcg()); + return true; + } + if (isThisExpression && !controller.isInClosure()) { + receiverType = controller.getClassNode(); + } + if (makeSetPrivateFieldWithBridgeMethod(receiver, receiverType, property, arguments, safe, spreadSafe, implicitThis)) return true; + } + return false; + } + + @SuppressWarnings("unchecked") + private boolean makeSetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final Expression arguments, final boolean safe, final boolean spreadSafe, final boolean implicitThis) { + WriterController controller = getController(); + FieldNode field = receiverType.getField(fieldName); + ClassNode outerClass = receiverType.getOuterClass(); + if (field == null && implicitThis && outerClass != null && !receiverType.isStaticClass()) { + Expression pexp; + if (controller.isInClosure()) { + MethodCallExpression mce = new MethodCallExpression( + new VariableExpression("this"), + "getThisObject", + ArgumentListExpression.EMPTY_ARGUMENTS + ); + mce.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, controller.getOutermostClass()); + mce.setImplicitThis(true); + mce.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD); + pexp = new CastExpression(controller.getOutermostClass(),mce); + } else { + pexp = new PropertyExpression( + new ClassExpression(outerClass), + "this" + ); + ((PropertyExpression)pexp).setImplicitThis(true); + } + pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, outerClass); + pexp.setSourcePosition(receiver); + return makeSetPrivateFieldWithBridgeMethod(pexp, outerClass, fieldName, arguments, safe, spreadSafe, true); + } + ClassNode classNode = controller.getClassNode(); + if (field != null && Modifier.isPrivate(field.getModifiers()) + && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode) || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode,receiverType)) + && !receiverType.equals(classNode)) { + Map<String, MethodNode> mutators = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS); + if (mutators != null) { + MethodNode methodNode = mutators.get(fieldName); + if (methodNode != null) { + MethodCallExpression mce = new MethodCallExpression(receiver, methodNode.getName(), + new ArgumentListExpression(field.isStatic()?new ConstantExpression(null):receiver, arguments)); + mce.setMethodTarget(methodNode); + mce.setSafe(safe); + mce.setSpreadSafe(spreadSafe); + mce.setImplicitThis(implicitThis); + mce.visit(controller.getAcg()); + return true; + } + } + } + return false; + } + + @Override + protected void assignToArray(Expression parent, Expression receiver, Expression index, Expression rhsValueLoader, boolean safe) { + ClassNode current = getController().getClassNode(); + ClassNode arrayType = getController().getTypeChooser().resolveType(receiver, current); + ClassNode arrayComponentType = arrayType.getComponentType(); + int operationType = getOperandType(arrayComponentType); + BinaryExpressionWriter bew = binExpWriter[operationType]; + + if (bew.arraySet(true) && arrayType.isArray() && !safe) { + super.assignToArray(parent, receiver, index, rhsValueLoader, safe); + } else { + /****** + / This code path is needed because ACG creates array access expressions + *******/ + + WriterController controller = getController(); + StaticTypeCheckingVisitor visitor = new StaticCompilationVisitor(controller.getSourceUnit(), controller.getClassNode()); + // let's replace this assignment to a subscript operator with a + // method call + // e.g. x[5] = 10 + // -> (x, [], 5), =, 10 + // -> methodCall(x, "putAt", [5, 10]) + ArgumentListExpression ae = new ArgumentListExpression(index, rhsValueLoader); + if (rhsValueLoader instanceof VariableSlotLoader && parent instanceof BinaryExpression) { + // GROOVY-6061 + rhsValueLoader.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, + controller.getTypeChooser().resolveType(parent, controller.getClassNode())); + } + MethodCallExpression mce = new MethodCallExpression( + receiver, + "putAt", + ae + ); + + mce.setSafe(safe); + mce.setSourcePosition(parent); + visitor.visitMethodCallExpression(mce); + OperandStack operandStack = controller.getOperandStack(); + int height = operandStack.getStackLength(); + mce.visit(controller.getAcg()); + operandStack.pop(); + operandStack.remove(operandStack.getStackLength()-height); + // return value of assignment + rhsValueLoader.visit(controller.getAcg()); + } + } + +}
