org.eclipse.jdt.internal.compiler.ast.EqualExpression.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.compiler.ast.EqualExpression.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contributions for
 *                        bug 186342 - [compiler][null] Using annotations for null checking
 *                        bug 331649 - [compiler][null] consider null annotations for fields
 *                        bug 383368 - [compiler][null] syntactic null analysis for field references
 *                        bug 382069 - [null] Make the null analysis consider JUnit's assertNotNull similarly to assertions
 *                        bug 403086 - [compiler][null] include the effect of 'assert' in syntactic null analysis for fields
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;

public class EqualExpression extends BinaryExpression {

    public EqualExpression(Expression left, Expression right, int operator) {
        super(left, right, operator);
    }

    private void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo,
            FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) {

        // collect null status of child nodes:
        int rightStatus = this.right.nullStatus(flowInfo, flowContext);
        int leftStatus = this.left.nullStatus(flowInfo, flowContext);

        boolean leftNonNullChecked = false;
        boolean rightNonNullChecked = false;

        // check if either is a non-local expression known to be nonnull and compared to null, candidates are
        // - method/field annotated @NonNull
        // - allocation expression, some literals, this reference (see inside expressionNonNullComparison(..))
        // these checks do not leverage the flowInfo.
        boolean checkEquality = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
        if ((flowContext.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING_MASK) == 0) {
            if (leftStatus == FlowInfo.NON_NULL && rightStatus == FlowInfo.NULL) {
                leftNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.left, checkEquality);
            } else if (leftStatus == FlowInfo.NULL && rightStatus == FlowInfo.NON_NULL) {
                rightNonNullChecked = scope.problemReporter().expressionNonNullComparison(this.right,
                        checkEquality);
            }
        }

        boolean contextualCheckEquality = checkEquality
                ^ ((flowContext.tagBits & FlowContext.INSIDE_NEGATION) != 0);
        // perform flowInfo-based checks for variables and record info for syntactic null analysis for fields:
        if (!leftNonNullChecked) {
            LocalVariableBinding local = this.left.localVariableBinding();
            if (local != null) {
                if ((local.type.tagBits & TagBits.IsBaseType) == 0) {
                    checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local,
                            rightStatus, this.left);
                }
            } else if (this.left instanceof Reference
                    && ((!contextualCheckEquality && rightStatus == FlowInfo.NULL)
                            || (contextualCheckEquality && rightStatus == FlowInfo.NON_NULL))
                    && scope.compilerOptions().enableSyntacticNullAnalysisForFields) {
                FieldBinding field = ((Reference) this.left).lastFieldBinding();
                if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
                    flowContext.recordNullCheckedFieldReference((Reference) this.left, 1);
                }
            }
        }
        if (!rightNonNullChecked) {
            LocalVariableBinding local = this.right.localVariableBinding();
            if (local != null) {
                if ((local.type.tagBits & TagBits.IsBaseType) == 0) {
                    checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local,
                            leftStatus, this.right);
                }
            } else if (this.right instanceof Reference
                    && ((!contextualCheckEquality && leftStatus == FlowInfo.NULL)
                            || (contextualCheckEquality && leftStatus == FlowInfo.NON_NULL))
                    && scope.compilerOptions().enableSyntacticNullAnalysisForFields) {
                FieldBinding field = ((Reference) this.right).lastFieldBinding();
                if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
                    flowContext.recordNullCheckedFieldReference((Reference) this.right, 1);
                }
            }
        }

        // handle reachability:
        if (leftNonNullChecked || rightNonNullChecked) {
            // above checks have not propagated unreachable into the corresponding branch, do it now:
            if (checkEquality) {
                initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
            } else {
                initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
            }
        }
    }

    private void checkVariableComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo,
            FlowInfo initsWhenTrue, FlowInfo initsWhenFalse, LocalVariableBinding local, int nullStatus,
            Expression reference) {
        switch (nullStatus) {
        case FlowInfo.NULL:
            if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
                flowContext.recordUsingNullReference(scope, local, reference,
                        FlowContext.CAN_ONLY_NULL_NON_NULL | FlowContext.IN_COMPARISON_NULL, flowInfo);
                initsWhenTrue.markAsComparedEqualToNull(local); // from thereon it is set
                initsWhenFalse.markAsComparedEqualToNonNull(local); // from thereon it is set
            } else {
                flowContext.recordUsingNullReference(scope, local, reference,
                        FlowContext.CAN_ONLY_NULL_NON_NULL | FlowContext.IN_COMPARISON_NON_NULL, flowInfo);
                initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set
                initsWhenFalse.markAsComparedEqualToNull(local); // from thereon it is set
            }
            break;
        case FlowInfo.NON_NULL:
            if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
                flowContext.recordUsingNullReference(scope, local, reference,
                        FlowContext.CAN_ONLY_NULL | FlowContext.IN_COMPARISON_NON_NULL, flowInfo);
                initsWhenTrue.markAsComparedEqualToNonNull(local); // from thereon it is set
            } else {
                flowContext.recordUsingNullReference(scope, local, reference,
                        FlowContext.CAN_ONLY_NULL | FlowContext.IN_COMPARISON_NULL, flowInfo);
            }
            break;
        }
        // we do not impact enclosing try context because this kind of protection
        // does not preclude the variable from being null in an enclosing scope
    }

    private void analyzeLocalVariable(Expression exp, FlowInfo flowInfo) {
        if (exp instanceof SingleNameReference && (exp.bits & Binding.LOCAL) != 0) {
            LocalVariableBinding localBinding = (LocalVariableBinding) ((SingleNameReference) exp).binding;
            if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) {
                localBinding.useFlag = LocalVariableBinding.USED;
            } else if (localBinding.useFlag == LocalVariableBinding.UNUSED) {
                localBinding.useFlag = LocalVariableBinding.FAKE_USED;
            }
        }
    }

    @Override
    public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
        FlowInfo result;
        if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
            if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.typeID() == T_boolean)) {
                if (this.left.constant.booleanValue()) { //  true == anything
                    //  this is equivalent to the right argument inits
                    result = this.right.analyseCode(currentScope, flowContext, flowInfo);
                } else { // false == anything
                    //  this is equivalent to the right argument inits negated
                    result = this.right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
                    analyzeLocalVariable(this.left, flowInfo);
                }
            } else if ((this.right.constant != Constant.NotAConstant)
                    && (this.right.constant.typeID() == T_boolean)) {
                if (this.right.constant.booleanValue()) { //  anything == true
                    //  this is equivalent to the left argument inits
                    result = this.left.analyseCode(currentScope, flowContext, flowInfo);
                } else { // anything == false
                    //  this is equivalent to the right argument inits negated
                    result = this.left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
                    analyzeLocalVariable(this.right, flowInfo);
                }
            } else {
                result = this.right
                        .analyseCode(currentScope, flowContext,
                                this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits())
                        .unconditionalInits();
            }
        } else { //NOT_EQUAL :
            if ((this.left.constant != Constant.NotAConstant) && (this.left.constant.typeID() == T_boolean)) {
                if (!this.left.constant.booleanValue()) { //  false != anything
                    //  this is equivalent to the right argument inits
                    result = this.right.analyseCode(currentScope, flowContext, flowInfo);
                    analyzeLocalVariable(this.left, flowInfo);
                } else { // true != anything
                    //  this is equivalent to the right argument inits negated
                    result = this.right.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
                }
            } else if ((this.right.constant != Constant.NotAConstant)
                    && (this.right.constant.typeID() == T_boolean)) {
                if (!this.right.constant.booleanValue()) { //  anything != false
                    //  this is equivalent to the right argument inits
                    result = this.left.analyseCode(currentScope, flowContext, flowInfo);
                    analyzeLocalVariable(this.right, flowInfo);
                } else { // anything != true
                    //  this is equivalent to the right argument inits negated
                    result = this.left.analyseCode(currentScope, flowContext, flowInfo).asNegatedCondition();
                }
            } else {
                result = this.right.analyseCode(currentScope, flowContext,
                        this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits()).
                /* unneeded since we flatten it: asNegatedCondition(). */
                        unconditionalInits();
            }
        }
        if (result instanceof UnconditionalFlowInfo && (result.tagBits & FlowInfo.UNREACHABLE) == 0) { // the flow info is flat
            result = FlowInfo.conditional(result.copy(), result.copy());
            // TODO (maxime) check, reintroduced copy
        }
        checkNullComparison(currentScope, flowContext, result, result.initsWhenTrue(), result.initsWhenFalse());
        return result;
    }

    public final void computeConstant(TypeBinding leftType, TypeBinding rightType) {
        if ((this.left.constant != Constant.NotAConstant) && (this.right.constant != Constant.NotAConstant)) {
            this.constant = Constant.computeConstantOperationEQUAL_EQUAL(this.left.constant, leftType.id,
                    this.right.constant, rightType.id);
            if (((this.bits & OperatorMASK) >> OperatorSHIFT) == NOT_EQUAL)
                this.constant = BooleanConstant.fromValue(!this.constant.booleanValue());
        } else {
            this.constant = Constant.NotAConstant;
            // no optimization for null == null
        }
    }

    /**
     * Normal == or != code generation.
     *
     * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
     * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
     * @param valueRequired boolean
     */
    @Override
    public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

        int pc = codeStream.position;
        if (this.constant != Constant.NotAConstant) {
            if (valueRequired)
                codeStream.generateConstant(this.constant, this.implicitConversion);
            codeStream.recordPositionsFrom(pc, this.sourceStart);
            return;
        }

        if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
            generateBooleanEqual(currentScope, codeStream, valueRequired);
        } else {
            generateNonBooleanEqual(currentScope, codeStream, valueRequired);
        }
        if (valueRequired) {
            codeStream.generateImplicitConversion(this.implicitConversion);
        }
        codeStream.recordPositionsFrom(pc, this.sourceStart);
    }

    /**
     * Boolean operator code generation
     *   Optimized operations are: == and !=
     */
    @Override
    public void generateOptimizedBoolean(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel,
            BranchLabel falseLabel, boolean valueRequired) {

        if (this.constant != Constant.NotAConstant) {
            super.generateOptimizedBoolean(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
            return;
        }
        if (((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL) {
            if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
                generateOptimizedBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
            } else {
                generateOptimizedNonBooleanEqual(currentScope, codeStream, trueLabel, falseLabel, valueRequired);
            }
        } else {
            if ((this.left.implicitConversion & COMPILE_TYPE_MASK) /*compile-time*/ == T_boolean) {
                generateOptimizedBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired);
            } else {
                generateOptimizedNonBooleanEqual(currentScope, codeStream, falseLabel, trueLabel, valueRequired);
            }
        }
    }

    /**
     * Boolean generation for == with boolean operands
     *
     * Note this code does not optimize conditional constants !!!!
     */
    public void generateBooleanEqual(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

        // optimized cases: <something equivalent to true> == x, <something equivalent to false> == x,
        // optimized cases: <something equivalent to false> != x, <something equivalent to true> != x,
        boolean isEqualOperator = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
        Constant cst = this.left.optimizedBooleanConstant();
        if (cst != Constant.NotAConstant) {
            Constant rightCst = this.right.optimizedBooleanConstant();
            if (rightCst != Constant.NotAConstant) {
                // <something equivalent to true> == <something equivalent to true>, <something equivalent to false> != <something equivalent to true>
                // <something equivalent to true> == <something equivalent to false>, <something equivalent to false> != <something equivalent to false>
                this.left.generateCode(currentScope, codeStream, false);
                this.right.generateCode(currentScope, codeStream, false);
                if (valueRequired) {
                    boolean leftBool = cst.booleanValue();
                    boolean rightBool = rightCst.booleanValue();
                    if (isEqualOperator) {
                        if (leftBool == rightBool) {
                            codeStream.iconst_1();
                        } else {
                            codeStream.iconst_0();
                        }
                    } else {
                        if (leftBool != rightBool) {
                            codeStream.iconst_1();
                        } else {
                            codeStream.iconst_0();
                        }
                    }
                }
            } else if (cst.booleanValue() == isEqualOperator) {
                // <something equivalent to true> == x, <something equivalent to false> != x
                this.left.generateCode(currentScope, codeStream, false);
                this.right.generateCode(currentScope, codeStream, valueRequired);
            } else {
                // <something equivalent to false> == x, <something equivalent to true> != x
                if (valueRequired) {
                    BranchLabel falseLabel = new BranchLabel(codeStream);
                    this.left.generateCode(currentScope, codeStream, false);
                    this.right.generateOptimizedBoolean(currentScope, codeStream, null, falseLabel, valueRequired);
                    // comparison is TRUE
                    codeStream.iconst_0();
                    if ((this.bits & IsReturnedValue) != 0) {
                        codeStream.generateImplicitConversion(this.implicitConversion);
                        codeStream.generateReturnBytecode(this);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_1();
                    } else {
                        BranchLabel endLabel = new BranchLabel(codeStream);
                        codeStream.goto_(endLabel);
                        codeStream.decrStackSize(1);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_1();
                        endLabel.place();
                    }
                } else {
                    this.left.generateCode(currentScope, codeStream, false);
                    this.right.generateCode(currentScope, codeStream, false);
                }
                //            left.generateCode(currentScope, codeStream, false);
                //            right.generateCode(currentScope, codeStream, valueRequired);
                //            if (valueRequired) {
                //               codeStream.iconst_1();
                //               codeStream.ixor(); // negate
                //            }
            }
            return;
        }
        cst = this.right.optimizedBooleanConstant();
        if (cst != Constant.NotAConstant) {
            if (cst.booleanValue() == isEqualOperator) {
                // x == <something equivalent to true>, x != <something equivalent to false>
                this.left.generateCode(currentScope, codeStream, valueRequired);
                this.right.generateCode(currentScope, codeStream, false);
            } else {
                // x == <something equivalent to false>, x != <something equivalent to true>
                if (valueRequired) {
                    BranchLabel falseLabel = new BranchLabel(codeStream);
                    this.left.generateOptimizedBoolean(currentScope, codeStream, null, falseLabel, valueRequired);
                    this.right.generateCode(currentScope, codeStream, false);
                    // comparison is TRUE
                    codeStream.iconst_0();
                    if ((this.bits & IsReturnedValue) != 0) {
                        codeStream.generateImplicitConversion(this.implicitConversion);
                        codeStream.generateReturnBytecode(this);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_1();
                    } else {
                        BranchLabel endLabel = new BranchLabel(codeStream);
                        codeStream.goto_(endLabel);
                        codeStream.decrStackSize(1);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_1();
                        endLabel.place();
                    }
                } else {
                    this.left.generateCode(currentScope, codeStream, false);
                    this.right.generateCode(currentScope, codeStream, false);
                }
                //            left.generateCode(currentScope, codeStream, valueRequired);
                //            right.generateCode(currentScope, codeStream, false);
                //            if (valueRequired) {
                //               codeStream.iconst_1();
                //               codeStream.ixor(); // negate
                //            }
            }
            return;
        }
        // default case
        this.left.generateCode(currentScope, codeStream, valueRequired);
        this.right.generateCode(currentScope, codeStream, valueRequired);

        if (valueRequired) {
            if (isEqualOperator) {
                BranchLabel falseLabel;
                codeStream.if_icmpne(falseLabel = new BranchLabel(codeStream));
                // comparison is TRUE
                codeStream.iconst_1();
                if ((this.bits & IsReturnedValue) != 0) {
                    codeStream.generateImplicitConversion(this.implicitConversion);
                    codeStream.generateReturnBytecode(this);
                    // comparison is FALSE
                    falseLabel.place();
                    codeStream.iconst_0();
                } else {
                    BranchLabel endLabel = new BranchLabel(codeStream);
                    codeStream.goto_(endLabel);
                    codeStream.decrStackSize(1);
                    // comparison is FALSE
                    falseLabel.place();
                    codeStream.iconst_0();
                    endLabel.place();
                }
            } else {
                codeStream.ixor();
            }
        }
    }

    /**
     * Boolean generation for == with boolean operands
     *
     * Note this code does not optimize conditional constants !!!!
     */
    public void generateOptimizedBooleanEqual(BlockScope currentScope, CodeStream codeStream, BranchLabel trueLabel,
            BranchLabel falseLabel, boolean valueRequired) {

        // optimized cases: true == x, false == x
        if (this.left.constant != Constant.NotAConstant) {
            boolean inline = this.left.constant.booleanValue();
            this.right.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel),
                    (inline ? falseLabel : trueLabel), valueRequired);
            return;
        } // optimized cases: x == true, x == false
        if (this.right.constant != Constant.NotAConstant) {
            boolean inline = this.right.constant.booleanValue();
            this.left.generateOptimizedBoolean(currentScope, codeStream, (inline ? trueLabel : falseLabel),
                    (inline ? falseLabel : trueLabel), valueRequired);
            return;
        }
        // default case
        this.left.generateCode(currentScope, codeStream, valueRequired);
        this.right.generateCode(currentScope, codeStream, valueRequired);
        int pc = codeStream.position;
        if (valueRequired) {
            if (falseLabel == null) {
                if (trueLabel != null) {
                    // implicit falling through the FALSE case
                    codeStream.if_icmpeq(trueLabel);
                }
            } else {
                // implicit falling through the TRUE case
                if (trueLabel == null) {
                    codeStream.if_icmpne(falseLabel);
                } else {
                    // no implicit fall through TRUE/FALSE --> should never occur
                }
            }
        }
        codeStream.recordPositionsFrom(pc, this.sourceEnd);
    }

    /**
     * Boolean generation for == with non-boolean operands
     *
     */
    public void generateNonBooleanEqual(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {

        boolean isEqualOperator = ((this.bits & OperatorMASK) >> OperatorSHIFT) == EQUAL_EQUAL;
        if (((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int) {
            Constant cst;
            if ((cst = this.left.constant) != Constant.NotAConstant && cst.intValue() == 0) {
                // optimized case: 0 == x, 0 != x
                this.right.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    BranchLabel falseLabel = new BranchLabel(codeStream);
                    if (isEqualOperator) {
                        codeStream.ifne(falseLabel);
                    } else {
                        codeStream.ifeq(falseLabel);
                    }
                    // comparison is TRUE
                    codeStream.iconst_1();
                    if ((this.bits & IsReturnedValue) != 0) {
                        codeStream.generateImplicitConversion(this.implicitConversion);
                        codeStream.generateReturnBytecode(this);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                    } else {
                        BranchLabel endLabel = new BranchLabel(codeStream);
                        codeStream.goto_(endLabel);
                        codeStream.decrStackSize(1);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                        endLabel.place();
                    }
                }
                return;
            }
            if ((cst = this.right.constant) != Constant.NotAConstant && cst.intValue() == 0) {
                // optimized case: x == 0, x != 0
                this.left.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    BranchLabel falseLabel = new BranchLabel(codeStream);
                    if (isEqualOperator) {
                        codeStream.ifne(falseLabel);
                    } else {
                        codeStream.ifeq(falseLabel);
                    }
                    // comparison is TRUE
                    codeStream.iconst_1();
                    if ((this.bits & IsReturnedValue) != 0) {
                        codeStream.generateImplicitConversion(this.implicitConversion);
                        codeStream.generateReturnBytecode(this);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                    } else {
                        BranchLabel endLabel = new BranchLabel(codeStream);
                        codeStream.goto_(endLabel);
                        codeStream.decrStackSize(1);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                        endLabel.place();
                    }
                }
                return;
            }
        }

        // null cases
        if (this.right instanceof NullLiteral) {
            if (this.left instanceof NullLiteral) {
                // null == null, null != null
                if (valueRequired) {
                    if (isEqualOperator) {
                        codeStream.iconst_1();
                    } else {
                        codeStream.iconst_0();
                    }
                }
            } else {
                // x == null, x != null
                this.left.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    BranchLabel falseLabel = new BranchLabel(codeStream);
                    if (isEqualOperator) {
                        codeStream.ifnonnull(falseLabel);
                    } else {
                        codeStream.ifnull(falseLabel);
                    }
                    // comparison is TRUE
                    codeStream.iconst_1();
                    if ((this.bits & IsReturnedValue) != 0) {
                        codeStream.generateImplicitConversion(this.implicitConversion);
                        codeStream.generateReturnBytecode(this);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                    } else {
                        BranchLabel endLabel = new BranchLabel(codeStream);
                        codeStream.goto_(endLabel);
                        codeStream.decrStackSize(1);
                        // comparison is FALSE
                        falseLabel.place();
                        codeStream.iconst_0();
                        endLabel.place();
                    }
                }
            }
            return;
        } else if (this.left instanceof NullLiteral) {
            // null = x, null != x
            this.right.generateCode(currentScope, codeStream, valueRequired);
            if (valueRequired) {
                BranchLabel falseLabel = new BranchLabel(codeStream);
                if (isEqualOperator) {
                    codeStream.ifnonnull(falseLabel);
                } else {
                    codeStream.ifnull(falseLabel);
                }
                // comparison is TRUE
                codeStream.iconst_1();
                if ((this.bits & IsReturnedValue) != 0) {
                    codeStream.generateImplicitConversion(this.implicitConversion);
                    codeStream.generateReturnBytecode(this);
                    // comparison is FALSE
                    falseLabel.place();
                    codeStream.iconst_0();
                } else {
                    BranchLabel endLabel = new BranchLabel(codeStream);
                    codeStream.goto_(endLabel);
                    codeStream.decrStackSize(1);
                    // comparison is FALSE
                    falseLabel.place();
                    codeStream.iconst_0();
                    endLabel.place();
                }
            }
            return;
        }

        // default case
        this.left.generateCode(currentScope, codeStream, valueRequired);
        this.right.generateCode(currentScope, codeStream, valueRequired);
        if (valueRequired) {
            BranchLabel falseLabel = new BranchLabel(codeStream);
            if (isEqualOperator) {
                switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
                case T_int:
                    codeStream.if_icmpne(falseLabel);
                    break;
                case T_float:
                    codeStream.fcmpl();
                    codeStream.ifne(falseLabel);
                    break;
                case T_long:
                    codeStream.lcmp();
                    codeStream.ifne(falseLabel);
                    break;
                case T_double:
                    codeStream.dcmpl();
                    codeStream.ifne(falseLabel);
                    break;
                default:
                    codeStream.if_acmpne(falseLabel);
                }
            } else {
                switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
                case T_int:
                    codeStream.if_icmpeq(falseLabel);
                    break;
                case T_float:
                    codeStream.fcmpl();
                    codeStream.ifeq(falseLabel);
                    break;
                case T_long:
                    codeStream.lcmp();
                    codeStream.ifeq(falseLabel);
                    break;
                case T_double:
                    codeStream.dcmpl();
                    codeStream.ifeq(falseLabel);
                    break;
                default:
                    codeStream.if_acmpeq(falseLabel);
                }
            }
            // comparison is TRUE
            codeStream.iconst_1();
            if ((this.bits & IsReturnedValue) != 0) {
                codeStream.generateImplicitConversion(this.implicitConversion);
                codeStream.generateReturnBytecode(this);
                // comparison is FALSE
                falseLabel.place();
                codeStream.iconst_0();
            } else {
                BranchLabel endLabel = new BranchLabel(codeStream);
                codeStream.goto_(endLabel);
                codeStream.decrStackSize(1);
                // comparison is FALSE
                falseLabel.place();
                codeStream.iconst_0();
                endLabel.place();
            }
        }
    }

    /**
     * Boolean generation for == with non-boolean operands
     *
     */
    public void generateOptimizedNonBooleanEqual(BlockScope currentScope, CodeStream codeStream,
            BranchLabel trueLabel, BranchLabel falseLabel, boolean valueRequired) {

        int pc = codeStream.position;
        Constant inline;
        if ((inline = this.right.constant) != Constant.NotAConstant) {
            // optimized case: x == 0
            if ((((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int)
                    && (inline.intValue() == 0)) {
                this.left.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    if (falseLabel == null) {
                        if (trueLabel != null) {
                            // implicit falling through the FALSE case
                            codeStream.ifeq(trueLabel);
                        }
                    } else {
                        // implicit falling through the TRUE case
                        if (trueLabel == null) {
                            codeStream.ifne(falseLabel);
                        } else {
                            // no implicit fall through TRUE/FALSE --> should never occur
                        }
                    }
                }
                codeStream.recordPositionsFrom(pc, this.sourceStart);
                return;
            }
        }
        if ((inline = this.left.constant) != Constant.NotAConstant) {
            // optimized case: 0 == x
            if ((((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_int)
                    && (inline.intValue() == 0)) {
                this.right.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    if (falseLabel == null) {
                        if (trueLabel != null) {
                            // implicit falling through the FALSE case
                            codeStream.ifeq(trueLabel);
                        }
                    } else {
                        // implicit falling through the TRUE case
                        if (trueLabel == null) {
                            codeStream.ifne(falseLabel);
                        } else {
                            // no implicit fall through TRUE/FALSE --> should never occur
                        }
                    }
                }
                codeStream.recordPositionsFrom(pc, this.sourceStart);
                return;
            }
        }
        // null cases
        // optimized case: x == null
        if (this.right instanceof NullLiteral) {
            if (this.left instanceof NullLiteral) {
                // null == null
                if (valueRequired) {
                    if (falseLabel == null) {
                        // implicit falling through the FALSE case
                        if (trueLabel != null) {
                            codeStream.goto_(trueLabel);
                        }
                    }
                }
            } else {
                this.left.generateCode(currentScope, codeStream, valueRequired);
                if (valueRequired) {
                    if (falseLabel == null) {
                        if (trueLabel != null) {
                            // implicit falling through the FALSE case
                            codeStream.ifnull(trueLabel);
                        }
                    } else {
                        // implicit falling through the TRUE case
                        if (trueLabel == null) {
                            codeStream.ifnonnull(falseLabel);
                        } else {
                            // no implicit fall through TRUE/FALSE --> should never occur
                        }
                    }
                }
            }
            codeStream.recordPositionsFrom(pc, this.sourceStart);
            return;
        } else if (this.left instanceof NullLiteral) { // optimized case: null == x
            this.right.generateCode(currentScope, codeStream, valueRequired);
            if (valueRequired) {
                if (falseLabel == null) {
                    if (trueLabel != null) {
                        // implicit falling through the FALSE case
                        codeStream.ifnull(trueLabel);
                    }
                } else {
                    // implicit falling through the TRUE case
                    if (trueLabel == null) {
                        codeStream.ifnonnull(falseLabel);
                    } else {
                        // no implicit fall through TRUE/FALSE --> should never occur
                    }
                }
            }
            codeStream.recordPositionsFrom(pc, this.sourceStart);
            return;
        }

        // default case
        this.left.generateCode(currentScope, codeStream, valueRequired);
        this.right.generateCode(currentScope, codeStream, valueRequired);
        if (valueRequired) {
            if (falseLabel == null) {
                if (trueLabel != null) {
                    // implicit falling through the FALSE case
                    switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
                    case T_int:
                        codeStream.if_icmpeq(trueLabel);
                        break;
                    case T_float:
                        codeStream.fcmpl();
                        codeStream.ifeq(trueLabel);
                        break;
                    case T_long:
                        codeStream.lcmp();
                        codeStream.ifeq(trueLabel);
                        break;
                    case T_double:
                        codeStream.dcmpl();
                        codeStream.ifeq(trueLabel);
                        break;
                    default:
                        codeStream.if_acmpeq(trueLabel);
                    }
                }
            } else {
                // implicit falling through the TRUE case
                if (trueLabel == null) {
                    switch ((this.left.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) { // operand runtime type
                    case T_int:
                        codeStream.if_icmpne(falseLabel);
                        break;
                    case T_float:
                        codeStream.fcmpl();
                        codeStream.ifne(falseLabel);
                        break;
                    case T_long:
                        codeStream.lcmp();
                        codeStream.ifne(falseLabel);
                        break;
                    case T_double:
                        codeStream.dcmpl();
                        codeStream.ifne(falseLabel);
                        break;
                    default:
                        codeStream.if_acmpne(falseLabel);
                    }
                } else {
                    // no implicit fall through TRUE/FALSE --> should never occur
                }
            }
        }
        codeStream.recordPositionsFrom(pc, this.sourceStart);
    }

    @Override
    public boolean isCompactableOperation() {
        return false;
    }

    @Override
    protected Constant optimizedNullComparisonConstant() {
        int operator = (this.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
        if (operator == OperatorIds.EQUAL_EQUAL) {
            if (this.left instanceof NullLiteral && this.right instanceof NullLiteral) {
                return BooleanConstant.fromValue(true);
            }
        } else if (operator == OperatorIds.NOT_EQUAL) {
            if (this.left instanceof NullLiteral && this.right instanceof NullLiteral) {
                return BooleanConstant.fromValue(false);
            }
        }
        return Constant.NotAConstant;
    }

    @Override
    public TypeBinding resolveType(BlockScope scope) {

        boolean leftIsCast, rightIsCast;
        if ((leftIsCast = this.left instanceof CastExpression) == true)
            this.left.bits |= DisableUnnecessaryCastCheck; // will check later on
        TypeBinding originalLeftType = this.left.resolveType(scope);

        if ((rightIsCast = this.right instanceof CastExpression) == true)
            this.right.bits |= DisableUnnecessaryCastCheck; // will check later on
        TypeBinding originalRightType = this.right.resolveType(scope);

        // always return BooleanBinding
        if (originalLeftType == null || originalRightType == null) {
            this.constant = Constant.NotAConstant;
            return null;
        }

        final CompilerOptions compilerOptions = scope.compilerOptions();
        if (compilerOptions.complainOnUninternedIdentityComparison
                && originalRightType.hasTypeBit(TypeIds.BitUninternedType)
                && originalLeftType.hasTypeBit(TypeIds.BitUninternedType))
            scope.problemReporter().uninternedIdentityComparison(this, originalLeftType, originalRightType,
                    scope.referenceCompilationUnit());

        // autoboxing support
        boolean use15specifics = compilerOptions.sourceLevel >= ClassFileConstants.JDK1_5;
        TypeBinding leftType = originalLeftType, rightType = originalRightType;
        if (use15specifics) {
            if (leftType != TypeBinding.NULL && leftType.isBaseType()) {
                if (!rightType.isBaseType()) {
                    rightType = scope.environment().computeBoxingType(rightType);
                }
            } else {
                if (rightType != TypeBinding.NULL && rightType.isBaseType()) {
                    leftType = scope.environment().computeBoxingType(leftType);
                }
            }
        }
        // both base type
        if (leftType.isBaseType() && rightType.isBaseType()) {
            int leftTypeID = leftType.id;
            int rightTypeID = rightType.id;

            // the code is an int
            // (cast)  left   == (cast)  right --> result
            //  0000   0000       0000   0000      0000
            //  <<16   <<12       <<8    <<4       <<0
            int operatorSignature = OperatorSignatures[EQUAL_EQUAL][(leftTypeID << 4) + rightTypeID];
            this.left.computeConversion(scope,
                    TypeBinding.wellKnownType(scope, (operatorSignature >>> 16) & 0x0000F), originalLeftType);
            this.right.computeConversion(scope,
                    TypeBinding.wellKnownType(scope, (operatorSignature >>> 8) & 0x0000F), originalRightType);
            this.bits |= operatorSignature & 0xF;
            if ((operatorSignature & 0x0000F) == T_undefined) {
                this.constant = Constant.NotAConstant;
                scope.problemReporter().invalidOperator(this, leftType, rightType);
                return null;
            }
            // check need for operand cast
            if (leftIsCast || rightIsCast) {
                CastExpression.checkNeedForArgumentCasts(scope, EQUAL_EQUAL, operatorSignature, this.left,
                        leftType.id, leftIsCast, this.right, rightType.id, rightIsCast);
            }
            computeConstant(leftType, rightType);

            // check whether comparing identical expressions
            Binding leftDirect = Expression.getDirectBinding(this.left);
            if (leftDirect != null && leftDirect == Expression.getDirectBinding(this.right)) {
                if (leftTypeID != TypeIds.T_double && leftTypeID != TypeIds.T_float
                        && (!(this.right instanceof Assignment))) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=281776
                    scope.problemReporter().comparingIdenticalExpressions(this);
            } else if (this.constant != Constant.NotAConstant) {
                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276740
                int operator = (this.bits & OperatorMASK) >> OperatorSHIFT;
                if ((operator == EQUAL_EQUAL && this.constant == BooleanConstant.fromValue(true))
                        || (operator == NOT_EQUAL && this.constant == BooleanConstant.fromValue(false)))
                    scope.problemReporter().comparingIdenticalExpressions(this);
            }
            return this.resolvedType = TypeBinding.BOOLEAN;
        }

        // Object references
        // spec 15.20.3
        if ((!leftType.isBaseType() || leftType == TypeBinding.NULL) // cannot compare: Object == (int)0
                && (!rightType.isBaseType() || rightType == TypeBinding.NULL)
                && (checkCastTypesCompatibility(scope, leftType, rightType, null)
                        || checkCastTypesCompatibility(scope, rightType, leftType, null))) {

            // (special case for String)
            if ((rightType.id == T_JavaLangString) && (leftType.id == T_JavaLangString)) {
                computeConstant(leftType, rightType);
            } else {
                this.constant = Constant.NotAConstant;
            }
            TypeBinding objectType = scope.getJavaLangObject();
            this.left.computeConversion(scope, objectType, leftType);
            this.right.computeConversion(scope, objectType, rightType);
            // check need for operand cast
            boolean unnecessaryLeftCast = (this.left.bits & UnnecessaryCast) != 0;
            boolean unnecessaryRightCast = (this.right.bits & UnnecessaryCast) != 0;
            if (unnecessaryLeftCast || unnecessaryRightCast) {
                TypeBinding alternateLeftType = unnecessaryLeftCast
                        ? ((CastExpression) this.left).expression.resolvedType
                        : leftType;
                TypeBinding alternateRightType = unnecessaryRightCast
                        ? ((CastExpression) this.right).expression.resolvedType
                        : rightType;
                // Bug 543727 - check if either cast is really needed
                if (!isCastNeeded(alternateLeftType, alternateRightType)) {
                    if (checkCastTypesCompatibility(scope, alternateLeftType, alternateRightType, null)
                            || checkCastTypesCompatibility(scope, alternateRightType, alternateLeftType, null)) {
                        if (unnecessaryLeftCast)
                            scope.problemReporter().unnecessaryCast((CastExpression) this.left);
                        if (unnecessaryRightCast)
                            scope.problemReporter().unnecessaryCast((CastExpression) this.right);
                    }
                }
            }
            // check whether comparing identical expressions
            Binding leftDirect = Expression.getDirectBinding(this.left);
            if (leftDirect != null && leftDirect == Expression.getDirectBinding(this.right)) {
                if (!(this.right instanceof Assignment)) {
                    scope.problemReporter().comparingIdenticalExpressions(this);
                }
            }
            return this.resolvedType = TypeBinding.BOOLEAN;
        }
        this.constant = Constant.NotAConstant;
        scope.problemReporter().notCompatibleTypesError(this, leftType, rightType);
        return null;
    }

    private boolean isCastNeeded(TypeBinding leftType, TypeBinding rightType) {
        // Bug 543727 - if either type is parameterized and the other is a base type,
        // a cast is necessary, even if boxing the base type will result in a compatible
        // type.
        if (leftType.isParameterizedType()) {
            return rightType.isBaseType();
        }
        if (rightType.isParameterizedType()) {
            return leftType.isBaseType();
        }
        return false;
    }

    @Override
    public void traverse(ASTVisitor visitor, BlockScope scope) {
        if (visitor.visit(this, scope)) {
            this.left.traverse(visitor, scope);
            this.right.traverse(visitor, scope);
        }
        visitor.endVisit(this, scope);
    }
}