blob: b2a6b3c87fa4bf85b344b149625058aff74754b8 [file] [log] [blame]
/*
* 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.AttributeExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.LambdaExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
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.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.VariableSlotLoader;
import org.codehaus.groovy.classgen.asm.WriterController;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.TokenUtil;
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.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.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression;
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.ast.tools.GeneralUtils.args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.declX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
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;
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_FUNCTIONAL_INTERFACE_TYPE;
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE;
/**
* 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.
*/
public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpressionMultiTypeDispatcher implements Opcodes {
private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", Parameter.EMPTY_ARRAY);
private final AtomicInteger labelCounter = new AtomicInteger();
public StaticTypesBinaryExpressionMultiTypeDispatcher(final WriterController wc) {
super(wc);
}
@Override
protected void writePostOrPrefixMethod(final int op, final String method, final Expression expression, final Expression orig) {
MethodNode mn = orig.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
if (mn != null) {
controller.getOperandStack().pop();
MethodCallExpression call = callX(expression, method);
call.setMethodTarget(mn);
call.visit(controller.getAcg());
return;
}
ClassNode top = controller.getOperandStack().getTopOperand();
if (ClassHelper.isPrimitiveType(top) && (ClassHelper.isNumberType(top) || char_TYPE.equals(top))) {
MethodVisitor mv = controller.getMethodVisitor();
visitInsnByType(top, mv, ICONST_1, LCONST_1, FCONST_1, DCONST_1);
if ("next".equals(method)) {
visitInsnByType(top, mv, IADD, LADD, FADD, DADD);
} else {
visitInsnByType(top, mv, ISUB, LSUB, FSUB, DSUB);
}
return;
}
super.writePostOrPrefixMethod(op, method, expression, orig);
}
private static void visitInsnByType(final ClassNode top, final MethodVisitor mv, final int iInsn, final int lInsn, final int fInsn, final int dInsn) {
if (WideningCategories.isIntCategory(top) || char_TYPE.equals(top)) {
mv.visitInsn(iInsn);
} else if (long_TYPE.equals(top)) {
mv.visitInsn(lInsn);
} else if (float_TYPE.equals(top)) {
mv.visitInsn(fInsn);
} else if (double_TYPE.equals(top)) {
mv.visitInsn(dInsn);
}
}
@Override
protected void evaluateBinaryExpressionWithAssignment(final String method, final BinaryExpression expression) {
Expression leftExpression = expression.getLeftExpression();
if (leftExpression instanceof PropertyExpression) {
PropertyExpression pexp = (PropertyExpression) leftExpression;
BinaryExpression expressionWithoutAssignment = binX(
leftExpression,
Token.newSymbol(
TokenUtil.removeAssignment(expression.getOperation().getType()),
expression.getOperation().getStartLine(),
expression.getOperation().getStartColumn()
),
expression.getRightExpression()
);
expressionWithoutAssignment.copyNodeMetaData(expression);
expressionWithoutAssignment.setSafe(expression.isSafe());
expressionWithoutAssignment.setSourcePosition(expression);
if (makeSetProperty(
pexp.getObjectExpression(),
pexp.getProperty(),
expressionWithoutAssignment,
pexp.isSafe(),
pexp.isSpreadSafe(),
pexp.isImplicitThis(),
pexp instanceof AttributeExpression)) {
return;
}
}
super.evaluateBinaryExpressionWithAssignment(method, expression);
}
@Override
public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) {
Expression leftExpression = expression.getLeftExpression();
if (!defineVariable) {
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;
}
}
} else {
Expression rightExpression = expression.getRightExpression();
if (rightExpression instanceof LambdaExpression || rightExpression instanceof MethodReferenceExpression) {
rightExpression.putNodeMetaData(INFERRED_FUNCTIONAL_INTERFACE_TYPE, leftExpression.getNodeMetaData(INFERRED_TYPE));
}
}
// GROOVY-5620: spread-safe operator on LHS is not supported
if (leftExpression instanceof PropertyExpression
&& ((PropertyExpression) leftExpression).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(final BinaryExpression expression) {
PropertyExpression spreadExpression = (PropertyExpression) expression.getLeftExpression();
Expression receiver = spreadExpression.getObjectExpression();
int counter = labelCounter.incrementAndGet();
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
// create an empty arraylist
VariableExpression result = varX(this.getClass().getSimpleName() + "$spreadresult" + counter, ARRAYLIST_CLASSNODE);
ConstructorCallExpression newArrayList = ctorX(ARRAYLIST_CLASSNODE);
newArrayList.setNodeMetaData(DIRECT_METHOD_CALL_TARGET, ARRAYLIST_CONSTRUCTOR);
Expression decl = declX(result, newArrayList);
decl.visit(controller.getAcg());
// if (receiver != null)
receiver.visit(controller.getAcg());
Label ifnull = compileStack.createLocalLabel("ifnull_" + counter);
MethodVisitor mv = controller.getMethodVisitor();
mv.visitJumpInsn(IFNULL, ifnull);
operandStack.remove(1); // receiver consumed by if()
Label nonull = compileStack.createLocalLabel("nonull_" + counter);
mv.visitLabel(nonull);
ClassNode componentType = StaticTypeCheckingVisitor.inferLoopElementType(
controller.getTypeChooser().resolveType(receiver, controller.getClassNode()));
Parameter iterator = new Parameter(componentType, "for$it$" + counter);
VariableExpression iteratorAsVar = varX(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 = binX(pexp, expression.getOperation(), expression.getRightExpression());
MethodCallExpression add = callX(result, "add", assignment);
add.setMethodTarget(ARRAYLIST_ADD_METHOD);
// for (e in receiver) { result.add(e?.method(arguments) }
ForStatement stmt = new ForStatement(
iterator,
receiver,
stmt(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) {
ClassNode receiverType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
String property = message.getText();
boolean isThisExpression = isThisExpression(receiver);
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;
}
if (!Objects.equals(receiverType.getPackageName(), current.getPackageName())) {
return false;
}
if (!fn.isStatic()) {
receiver.visit(controller.getAcg());
}
arguments.visit(controller.getAcg());
OperandStack operandStack = controller.getOperandStack();
operandStack.doGroovyCast(fn.getOriginType());
MethodVisitor mv = controller.getMethodVisitor();
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 = getSetterName(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) {
if (!Modifier.isFinal(propertyNode.getModifiers())) {
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.isInGeneratedFunction()) {
receiverType = controller.getClassNode();
}
if (makeSetPrivateFieldWithBridgeMethod(receiver, receiverType, property, arguments, safe, spreadSafe, implicitThis)) {
return true;
}
}
return false;
}
private boolean makeSetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final Expression arguments, final boolean safe, final boolean spreadSafe, final boolean implicitThis) {
FieldNode field = receiverType.getField(fieldName);
ClassNode outerClass = receiverType.getOuterClass();
if (field == null && implicitThis && outerClass != null && !receiverType.isStaticClass()) {
Expression pexp;
if (controller.isInGeneratedFunction()) {
MethodCallExpression mce = callThisX("getThisObject");
mce.setImplicitThis(true);
mce.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD);
mce.putNodeMetaData(INFERRED_TYPE, controller.getOutermostClass());
pexp = castX(controller.getOutermostClass(), mce);
} else {
pexp = propX(classX(outerClass), "this");
((PropertyExpression) pexp).setImplicitThis(true);
}
pexp.putNodeMetaData(INFERRED_TYPE, outerClass);
pexp.setSourcePosition(receiver);
return makeSetPrivateFieldWithBridgeMethod(pexp, outerClass, fieldName, arguments, safe, spreadSafe, true);
}
ClassNode classNode = controller.getClassNode();
if (field != null && field.isPrivate() && !receiverType.equals(classNode)
&& (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)
|| StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode,receiverType))) {
Map<String, MethodNode> mutators = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS);
if (mutators != null) {
MethodNode methodNode = mutators.get(fieldName);
if (methodNode != null) {
MethodCallExpression call = callX(receiver, methodNode.getName(), args(field.isStatic() ? nullX() : receiver, arguments));
call.setImplicitThis(implicitThis);
call.setMethodTarget(methodNode);
call.setSafe(safe);
call.setSpreadSafe(spreadSafe);
call.visit(controller.getAcg());
return true;
}
}
}
return false;
}
@Override
protected void assignToArray(final Expression parent, final Expression receiver, final Expression index, final Expression rhsValueLoader, final boolean safe) {
ClassNode arrayType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
int operationType = getOperandType(arrayType.getComponentType());
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
*/
StaticTypeCheckingVisitor visitor = new StaticCompilationVisitor(controller.getSourceUnit(), controller.getClassNode());
// GROOVY-6061
if (rhsValueLoader instanceof VariableSlotLoader && parent instanceof BinaryExpression) {
rhsValueLoader.putNodeMetaData(INFERRED_TYPE, controller.getTypeChooser().resolveType(parent, 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])
MethodCallExpression call = callX(receiver, "putAt", args(index, rhsValueLoader));
call.setSafe(safe);
call.setSourcePosition(parent);
visitor.visitMethodCallExpression(call);
OperandStack operandStack = controller.getOperandStack();
int height = operandStack.getStackLength();
call.visit(controller.getAcg());
operandStack.pop();
operandStack.remove(operandStack.getStackLength() - height);
// return value of assignment
rhsValueLoader.visit(controller.getAcg());
}
}
}