blob: e5e27bcd4a8cd842aa275cf40826144289d74453 [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.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.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.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.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.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.groovy.util.BeanUtils.capitalize;
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;
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 final AtomicInteger labelCounter = new AtomicInteger();
private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", Parameter.EMPTY_ARRAY);
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();
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(ClassNode top, MethodVisitor mv, int iInsn, int lInsn, int fInsn, 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
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/Null 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(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;
}
if (!Objects.equals(receiverType.getPackageName(), current.getPackageName())) {
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" + 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());
}
}
}