/* * 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.transform.sc.transformers; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.OperandStack; import org.codehaus.groovy.classgen.asm.WriterController; import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser; import org.codehaus.groovy.transform.stc.ExtensionMethodNode; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments; import static org.objectweb.asm.Opcodes.*; public class BooleanExpressionTransformer { private final StaticCompilationTransformer transformer; public BooleanExpressionTransformer(StaticCompilationTransformer staticCompilationTransformer) { transformer = staticCompilationTransformer; } Expression transformBooleanExpression(final BooleanExpression booleanExpression) { if (booleanExpression instanceof NotExpression) { return transformer.superTransform(booleanExpression); } final Expression expression = booleanExpression.getExpression(); if (!(expression instanceof BinaryExpression)) { StaticTypesTypeChooser typeChooser = transformer.getTypeChooser(); final ClassNode type = typeChooser.resolveType(expression, transformer.getClassNode()); BooleanExpression transformed = new OptimizingBooleanExpression(transformer.transform(expression),type); transformed.setSourcePosition(booleanExpression); transformed.copyNodeMetaData(booleanExpression); return transformed; } return transformer.superTransform(booleanExpression); } private static boolean isExtended(ClassNode owner, Iterator<InnerClassNode> classes) { while (classes.hasNext()) { InnerClassNode next = classes.next(); if (next!=owner && next.isDerivedFrom(owner)) return true; } if (owner.getInnerClasses().hasNext()) { return isExtended(owner, owner.getInnerClasses()); } return false; } private static class OptimizingBooleanExpression extends BooleanExpression { private final Expression expression; private final ClassNode type; public OptimizingBooleanExpression(final Expression expression, final ClassNode type) { super(expression); this.expression = expression; // we must use the redirect node, otherwise InnerClassNode would not have the "correct" type this.type = type.redirect(); } @Override public Expression transformExpression(final ExpressionTransformer transformer) { Expression ret = new OptimizingBooleanExpression(transformer.transform(expression), type); ret.setSourcePosition(this); ret.copyNodeMetaData(this); return ret; } @Override public void visit(final GroovyCodeVisitor visitor) { if (visitor instanceof AsmClassGenerator) { AsmClassGenerator acg = (AsmClassGenerator) visitor; WriterController controller = acg.getController(); OperandStack os = controller.getOperandStack(); if (type.equals(ClassHelper.boolean_TYPE)) { expression.visit(visitor); os.doGroovyCast(ClassHelper.boolean_TYPE); return; } if (type.equals(ClassHelper.Boolean_TYPE)) { MethodVisitor mv = controller.getMethodVisitor(); expression.visit(visitor); Label unbox = new Label(); Label exit = new Label(); // check for null mv.visitInsn(DUP); mv.visitJumpInsn(IFNONNULL, unbox); mv.visitInsn(POP); mv.visitInsn(ICONST_0); mv.visitJumpInsn(GOTO, exit); mv.visitLabel(unbox); // unbox // GROOVY-6270 if (!os.getTopOperand().equals(type)) BytecodeHelper.doCast(mv, type); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); mv.visitLabel(exit); os.replace(ClassHelper.boolean_TYPE); return; } ClassNode top = type; if (ClassHelper.isPrimitiveType(top)) { expression.visit(visitor); // in case of null safe invocation, it is possible that what was supposed to be a primitive type // becomes the "null" constant, so we need to recheck top = controller.getOperandStack().getTopOperand(); if (ClassHelper.isPrimitiveType(top)) { if (top.equals(ClassHelper.int_TYPE) || top.equals(ClassHelper.byte_TYPE) || top.equals(ClassHelper.short_TYPE) || top.equals(ClassHelper.char_TYPE)) { // int on stack } else if (top.equals(ClassHelper.long_TYPE)) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitInsn(LCONST_0); mv.visitInsn(LCMP); controller.getOperandStack().replace(ClassHelper.boolean_TYPE); } else if (top.equals(ClassHelper.float_TYPE)) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitInsn(F2D); mv.visitInsn(DCONST_0); mv.visitInsn(DCMPG); controller.getOperandStack().replace(ClassHelper.boolean_TYPE); } else if (top.equals(ClassHelper.double_TYPE)) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitInsn(DCONST_0); mv.visitInsn(DCMPG); controller.getOperandStack().replace(ClassHelper.boolean_TYPE); } return; } } List<MethodNode> asBoolean = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), top, "asBoolean", ClassNode.EMPTY_ARRAY); if (asBoolean.size() == 1) { MethodNode node = asBoolean.get(0); if (node instanceof ExtensionMethodNode) { MethodNode dgmNode = ((ExtensionMethodNode) node).getExtensionMethodNode(); ClassNode owner = dgmNode.getParameters()[0].getType(); if (ClassHelper.OBJECT_TYPE.equals(owner)) { // we may inline a var!=null check instead of calling a helper method iff // (1) the class doesn't define an asBoolean method (already tested) // (2) no subclass defines an asBoolean method // For (2), we check that we are in one of those cases // (a) a final class // (b) a private inner class without subclass if (Modifier.isFinal(top.getModifiers()) || (top instanceof InnerClassNode && Modifier.isPrivate(top.getModifiers()) && !isExtended(top, top.getOuterClass().getInnerClasses())) ) { CompareToNullExpression expr = new CompareToNullExpression( expression, false ); expr.visit(acg); return; } } } } super.visit(visitor); } else { super.visit(visitor); } } } }