org.eclipse.jdt.internal.compiler.ast.EqualExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
Eclipse Compiler for Java(TM)
/*******************************************************************************
* Copyright (c) 2000, 2020 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 reference
&& ((contextualCheckEquality ? rightStatus == FlowInfo.NON_NULL : rightStatus == FlowInfo.NULL))
&& shouldPerformSyntacticAnalsysisFor(scope, reference))
{
FieldBinding field = reference.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 reference
&& ((contextualCheckEquality ? leftStatus == FlowInfo.NON_NULL : leftStatus == FlowInfo.NULL))
&& shouldPerformSyntacticAnalsysisFor(scope, reference))
{
FieldBinding field = reference.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);
}
}
}
boolean shouldPerformSyntacticAnalsysisFor(Scope scope, Reference reference) {
CompilerOptions compilerOptions = scope.compilerOptions();
if (compilerOptions.enableSyntacticNullAnalysisForFields)
return true;
if (compilerOptions.isAnnotationBasedResourceAnalysisEnabled && (reference.bits & Binding.FIELD) != 0) {
// annotation based resource leak analysis implicitly "borrows" from syntactic analysis for fields
// in order to understand the pattern "if (this.resource != null) this.resource.close();"
FieldBinding fieldBinding = reference.fieldBinding();
if (fieldBinding != null && fieldBinding.closeTracker != null)
return true;
}
return false;
}
public void syntacticFieldAnalysisForFalseBranch(FlowInfo flowInfo, FlowContext flowContext) {
// extracted slice of checkNullComparison concerning syntactic null analysis for fields:
// now compute the effect on the false branch and record it in flowContext:
int rightStatus = this.right.nullStatus(flowInfo, flowContext);
if (rightStatus == FlowInfo.NULL
&& this.left instanceof Reference
&& this.left.localVariableBinding() == null)
{
FieldBinding field = ((Reference)this.left).lastFieldBinding();
if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
flowContext.recordNullCheckedFieldReference((Reference) this.left, 1);
}
}
int leftStatus = this.left.nullStatus(flowInfo, flowContext);
if (leftStatus == FlowInfo.NULL
&& this.right instanceof Reference
&& this.right.localVariableBinding() == null)
{
FieldBinding field = ((Reference)this.right).lastFieldBinding();
if (field != null && (field.type.tagBits & TagBits.IsBaseType) == 0) {
flowContext.recordNullCheckedFieldReference((Reference) this.right, 1);
}
}
}
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: == x, == x,
// optimized cases: != x, != 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) {
// == , !=
// == , !=
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) {
// == x, != x
this.left.generateCode(currentScope, codeStream, false);
this.right.generateCode(currentScope, codeStream, valueRequired);
} else {
// == x, != 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 == , x !=
this.left.generateCode(currentScope, codeStream, valueRequired);
this.right.generateCode(currentScope, codeStream, false);
} else {
// x == , x !=
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, true)
|| checkCastTypesCompatibility(scope, rightType, leftType, null, true))) {
// (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, false)
|| checkCastTypesCompatibility(scope, alternateRightType, alternateLeftType, null, false)) {
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);
}
}