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
/******************************************************************************* * 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.
Params:
  • currentScope – org.eclipse.jdt.internal.compiler.lookup.BlockScope
  • codeStream – org.eclipse.jdt.internal.compiler.codegen.CodeStream
  • valueRequired – boolean
/** * 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 !=
/** * 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 !!!!
/** * 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 !!!!
/** * 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
/** * 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
/** * 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); } }