blob: d9188794e3a84f0af380d1ade1d7883044a8504d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2013 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 - Contributions for
* bug 186342 - [compiler][null] Using annotations for null checking
* bug 365519 - editorial cleanup after bug 186342 and bug 365387
* bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK
* bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations
* bug 385626 - @NonNull fails across loop boundaries
* bug 388996 - [compiler][resource] Incorrect 'potential resource leak'
* bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.Reference;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
/**
* Reflects the context of code analysis, keeping track of enclosing
* try statements, exception handlers, etc...
*/
public class FinallyFlowContext extends TryFlowContext {
Reference[] finalAssignments;
VariableBinding[] finalVariables;
int assignCount;
// the following three arrays are in sync regarding their indices:
LocalVariableBinding[] nullLocals; // slots can be null for checkType == IN_UNBOXING
ASTNode[] nullReferences; // Expressions for null checking, Statements for resource analysis
// cast to Expression is safe if corresponding nullCheckType != EXIT_RESOURCE
int[] nullCheckTypes;
int nullCount;
// see also the related field FlowContext#expectedTypes
// back reference to the flow context of the corresponding try statement
public FlowContext tryContext;
public FinallyFlowContext(FlowContext parent, ASTNode associatedNode, ExceptionHandlingFlowContext tryContext) {
super(parent, associatedNode);
this.tryContext = tryContext;
}
/**
* Given some contextual initialization info (derived from a try block or a catch block), this
* code will check that the subroutine context does not also initialize a final variable potentially set
* redundantly.
*/
public void complainOnDeferredChecks(FlowInfo flowInfo, BlockScope scope) {
// check redundant final assignments
for (int i = 0; i < this.assignCount; i++) {
VariableBinding variable = this.finalVariables[i];
if (variable == null) continue;
boolean complained = false; // remember if have complained on this final assignment
if (variable instanceof FieldBinding) {
// final field
if (flowInfo.isPotentiallyAssigned((FieldBinding)variable)) {
complained = true;
scope.problemReporter().duplicateInitializationOfBlankFinalField((FieldBinding)variable, this.finalAssignments[i]);
}
} else {
// final local variable
if (flowInfo.isPotentiallyAssigned((LocalVariableBinding)variable)) {
complained = true;
scope.problemReporter().duplicateInitializationOfFinalLocal(
(LocalVariableBinding) variable,
this.finalAssignments[i]);
}
}
// any reference reported at this level is removed from the parent context
// where it could also be reported again
if (complained) {
FlowContext currentContext = this.getLocalParent();
while (currentContext != null) {
//if (currentContext.isSubRoutine()) {
currentContext.removeFinalAssignmentIfAny(this.finalAssignments[i]);
//}
currentContext = currentContext.getLocalParent();
}
}
}
// check inconsistent null checks
if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { // within an enclosing loop, be conservative
for (int i = 0; i < this.nullCount; i++) {
ASTNode location = this.nullReferences[i];
switch (this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) {
case ASSIGN_TO_NONNULL:
int nullStatus = flowInfo.nullStatus(this.nullLocals[i]);
if (nullStatus != FlowInfo.NON_NULL) {
this.parent.recordNullityMismatch(scope, (Expression) location,
this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], nullStatus);
}
break;
case IN_UNBOXING:
checkUnboxing(scope, (Expression) location, flowInfo);
break;
default:
this.parent.recordUsingNullReference(scope, this.nullLocals[i],
this.nullReferences[i], this.nullCheckTypes[i], flowInfo);
}
}
}
else { // no enclosing loop, be as precise as possible right now
for (int i = 0; i < this.nullCount; i++) {
ASTNode location = this.nullReferences[i];
// final local variable
LocalVariableBinding local = this.nullLocals[i];
switch (this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) {
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
if ((this.nullCheckTypes[i] & ~HIDE_NULL_COMPARISON_WARNING_MASK) == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
if ((this.nullCheckTypes[i] & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, location);
}
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, location);
}
continue;
}
//$FALL-THROUGH$
case CAN_ONLY_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
case CAN_ONLY_NULL | IN_ASSIGNMENT:
case CAN_ONLY_NULL | IN_INSTANCEOF:
Expression expression = (Expression) location;
if (flowInfo.isDefinitelyNull(local)) {
switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, expression);
continue;
}
if ((this.nullCheckTypes[i] & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNull(local, expression);
}
continue;
case FlowContext.IN_COMPARISON_NON_NULL:
if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, expression);
continue;
}
scope.problemReporter().localVariableNullComparedToNonNull(local, expression);
continue;
case FlowContext.IN_ASSIGNMENT:
scope.problemReporter().localVariableRedundantNullAssignment(local, expression);
continue;
case FlowContext.IN_INSTANCEOF:
scope.problemReporter().localVariableNullInstanceof(local, expression);
continue;
}
} else if (flowInfo.isPotentiallyNull(local)) {
switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
this.nullReferences[i] = null;
if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, expression);
continue;
}
break;
case FlowContext.IN_COMPARISON_NON_NULL:
this.nullReferences[i] = null;
if (((this.nullCheckTypes[i] & CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK) == CAN_ONLY_NULL) && (expression.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, expression);
continue;
}
break;
}
}
break;
case MAY_NULL:
if (flowInfo.isDefinitelyNull(local)) {
scope.problemReporter().localVariableNullReference(local, location);
continue;
}
if (flowInfo.isPotentiallyNull(local)) {
scope.problemReporter().localVariablePotentialNullReference(local, location);
}
break;
case ASSIGN_TO_NONNULL:
int nullStatus = flowInfo.nullStatus(local);
if (nullStatus != FlowInfo.NON_NULL) {
char[][] annotationName = scope.environment().getNonNullAnnotationName();
scope.problemReporter().nullityMismatch((Expression) location, this.providedExpectedTypes[i][0], this.providedExpectedTypes[i][1], nullStatus, annotationName);
}
break;
case IN_UNBOXING:
checkUnboxing(scope, (Expression) location, flowInfo);
break;
default:
// should not happen
}
}
}
}
public String individualToString() {
StringBuffer buffer = new StringBuffer("Finally flow context"); //$NON-NLS-1$
buffer.append("[finalAssignments count - ").append(this.assignCount).append(']'); //$NON-NLS-1$
buffer.append("[nullReferences count - ").append(this.nullCount).append(']'); //$NON-NLS-1$
return buffer.toString();
}
public boolean isSubRoutine() {
return true;
}
protected boolean recordFinalAssignment(
VariableBinding binding,
Reference finalAssignment) {
if (this.assignCount == 0) {
this.finalAssignments = new Reference[5];
this.finalVariables = new VariableBinding[5];
} else {
if (this.assignCount == this.finalAssignments.length)
System.arraycopy(
this.finalAssignments,
0,
(this.finalAssignments = new Reference[this.assignCount * 2]),
0,
this.assignCount);
System.arraycopy(
this.finalVariables,
0,
(this.finalVariables = new VariableBinding[this.assignCount * 2]),
0,
this.assignCount);
}
this.finalAssignments[this.assignCount] = finalAssignment;
this.finalVariables[this.assignCount++] = binding;
return true;
}
public void recordUsingNullReference(Scope scope, LocalVariableBinding local,
ASTNode location, int checkType, FlowInfo flowInfo) {
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0 && !flowInfo.isDefinitelyUnknown(local)) {
// if reference is being recorded inside an assert, we will not raise redundant null check warnings
checkType |= (this.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING);
int checkTypeWithoutHideNullWarning = checkType & ~FlowContext.HIDE_NULL_COMPARISON_WARNING_MASK;
if ((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) { // within an enclosing loop, be conservative
switch (checkTypeWithoutHideNullWarning) {
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
case CAN_ONLY_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
case CAN_ONLY_NULL | IN_ASSIGNMENT:
case CAN_ONLY_NULL | IN_INSTANCEOF:
Expression reference = (Expression) location;
if (flowInfo.cannotBeNull(local)) {
if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, reference);
}
flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
} else if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL)) {
scope.problemReporter().localVariableNonNullComparedToNull(local, reference);
flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
}
return;
}
if (flowInfo.canOnlyBeNull(local)) {
switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, reference);
return;
}
if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
}
flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
return;
case FlowContext.IN_COMPARISON_NON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, reference);
return;
}
scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
return;
case FlowContext.IN_ASSIGNMENT:
scope.problemReporter().localVariableRedundantNullAssignment(local, reference);
return;
case FlowContext.IN_INSTANCEOF:
scope.problemReporter().localVariableNullInstanceof(local, reference);
return;
}
} else if (flowInfo.isPotentiallyNull(local)) {
switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, reference);
return;
}
break;
case FlowContext.IN_COMPARISON_NON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, reference);
return;
}
break;
}
}
break;
case MAY_NULL :
if (flowInfo.cannotBeNull(local)) {
return;
}
if (flowInfo.canOnlyBeNull(local)) {
scope.problemReporter().localVariableNullReference(local, location);
return;
}
break;
default:
// never happens
}
}
else { // no enclosing loop, be as precise as possible right now
switch (checkTypeWithoutHideNullWarning) {
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
if (checkTypeWithoutHideNullWarning == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, location);
}
flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, location);
flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
}
return;
}
//$FALL-THROUGH$
case CAN_ONLY_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
case CAN_ONLY_NULL | IN_ASSIGNMENT:
case CAN_ONLY_NULL | IN_INSTANCEOF:
Expression reference = (Expression) location;
if (flowInfo.isDefinitelyNull(local)) {
switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, reference);
return;
}
if ((checkType & HIDE_NULL_COMPARISON_WARNING) == 0) {
scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
}
flowInfo.initsWhenFalse().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
return;
case FlowContext.IN_COMPARISON_NON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariableNullReference(local, reference);
return;
}
scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
flowInfo.initsWhenTrue().setReachMode(FlowInfo.UNREACHABLE_BY_NULLANALYSIS);
return;
case FlowContext.IN_ASSIGNMENT:
scope.problemReporter().localVariableRedundantNullAssignment(local, reference);
return;
case FlowContext.IN_INSTANCEOF:
scope.problemReporter().localVariableNullInstanceof(local, reference);
return;
}
} else if (flowInfo.isPotentiallyNull(local)) {
switch(checkTypeWithoutHideNullWarning & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, reference);
return;
}
break;
case FlowContext.IN_COMPARISON_NON_NULL:
if (((checkTypeWithoutHideNullWarning & CHECK_MASK) == CAN_ONLY_NULL) && (reference.implicitConversion & TypeIds.UNBOXING) != 0) { // check for auto-unboxing first and report appropriate warning
scope.problemReporter().localVariablePotentialNullReference(local, reference);
return;
}
break;
}
}
break;
case MAY_NULL :
if (flowInfo.isDefinitelyNull(local)) {
scope.problemReporter().localVariableNullReference(local, location);
return;
}
if (flowInfo.isPotentiallyNull(local)) {
scope.problemReporter().localVariablePotentialNullReference(local, location);
return;
}
if (flowInfo.isDefinitelyNonNull(local)) {
return; // shortcut: cannot be null
}
break;
default:
// never happens
}
}
recordNullReference(local, location, checkType);
// prepare to re-check with try/catch flow info
}
}
void removeFinalAssignmentIfAny(Reference reference) {
for (int i = 0; i < this.assignCount; i++) {
if (this.finalAssignments[i] == reference) {
this.finalAssignments[i] = null;
this.finalVariables[i] = null;
return;
}
}
}
protected void recordNullReference(LocalVariableBinding local,
ASTNode expression, int checkType) {
if (this.nullCount == 0) {
this.nullLocals = new LocalVariableBinding[5];
this.nullReferences = new ASTNode[5];
this.nullCheckTypes = new int[5];
}
else if (this.nullCount == this.nullLocals.length) {
int newLength = this.nullCount * 2;
System.arraycopy(this.nullLocals, 0,
this.nullLocals = new LocalVariableBinding[newLength], 0,
this.nullCount);
System.arraycopy(this.nullReferences, 0,
this.nullReferences = new ASTNode[newLength], 0,
this.nullCount);
System.arraycopy(this.nullCheckTypes, 0,
this.nullCheckTypes = new int[newLength], 0,
this.nullCount);
}
this.nullLocals[this.nullCount] = local;
this.nullReferences[this.nullCount] = expression;
this.nullCheckTypes[this.nullCount++] = checkType;
}
public void recordUnboxing(Scope scope, Expression expression, int nullStatus, FlowInfo flowInfo) {
if (nullStatus == FlowInfo.NULL)
super.recordUnboxing(scope, expression, nullStatus, flowInfo);
else // defer checking:
recordNullReference(null, expression, IN_UNBOXING);
}
protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, int nullStatus, TypeBinding expectedType, int checkType) {
// cf. decision structure inside FinallyFlowContext.recordUsingNullReference(..)
if (nullStatus == FlowInfo.UNKNOWN ||
((this.tagBits & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0 && nullStatus != FlowInfo.NULL)) {
recordProvidedExpectedTypes(providedType, expectedType, this.nullCount);
recordNullReference(expression.localVariableBinding(), expression, checkType);
return true;
}
return false;
}
}