blob: b2c44551794c859709f4b8f2cfbc4fe2ef160007 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2012 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contribution for bug 186342 - [compiler][null] Using annotations for null checking
*******************************************************************************/
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) {
int rightStatus = this.right.nullStatus(flowInfo);
int leftStatus = this.left.nullStatus(flowInfo);
// check if either is a method annotated @NonNull and compared to null:
if (leftStatus == FlowInfo.NON_NULL && rightStatus == FlowInfo.NULL) {
if (this.left instanceof MessageSend) {
scope.problemReporter().messageSendRedundantCheckOnNonNull(((MessageSend) this.left).binding, this.left);
}
// TODO: handle all kinds of expressions (cf. also https://bugs.eclipse.org/364326)
} else if (leftStatus == FlowInfo.NULL && rightStatus == FlowInfo.NON_NULL) {
if (this.right instanceof MessageSend) {
scope.problemReporter().messageSendRedundantCheckOnNonNull(((MessageSend) this.right).binding, this.right);
}
// TODO: handle all kinds of expressions (cf. also https://bugs.eclipse.org/364326)
}
LocalVariableBinding local = this.left.localVariableBinding();
if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, rightStatus, this.left);
}
local = this.right.localVariableBinding();
if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
checkVariableComparison(scope, flowContext, flowInfo, initsWhenTrue, initsWhenFalse, local, leftStatus, this.right);
}
}
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
}
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();
}
}
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();
}
}
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);
} 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);
} 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
}
//{ObjectTeams: no warnings/errors for generated (null-)checks:
if ((this.bits & IsGenerated) == 0)
// SH}
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
*/
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 !=
*/
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);
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
}
}
}
// reposition the endPC
codeStream.updateLastRecordedEndPC(currentScope, codeStream.position);
}
/**
* 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);
}
public boolean isCompactableOperation() {
return false;
}
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;
}
// autoboxing support
boolean use15specifics = scope.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;
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;
}
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);
}
}