/* * 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.*; import org.codehaus.groovy.ast.expr.*; 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.*; 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.*; import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.*; /** * 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 = (Map<String, MethodNode>) 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()); } } }