blob: 514a214529db23435ce805250e81e2f8118ef889 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2009 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
*******************************************************************************/
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.codegen.BranchLabel;
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.VariableBinding;
/**
* Reflects the context of code analysis, keeping track of enclosing
* try statements, exception handlers, etc...
*/
public class LoopingFlowContext extends SwitchFlowContext {
public BranchLabel continueLabel;
public UnconditionalFlowInfo initsOnContinue = FlowInfo.DEAD_END;
private UnconditionalFlowInfo upstreamNullFlowInfo;
private LoopingFlowContext innerFlowContexts[] = null;
private UnconditionalFlowInfo innerFlowInfos[] = null;
private int innerFlowContextsCount = 0;
private LabelFlowContext breakTargetContexts[] = null;
private int breakTargetsCount = 0;
Reference finalAssignments[];
VariableBinding finalVariables[];
int assignCount = 0;
LocalVariableBinding[] nullLocals;
Expression[] nullReferences;
int[] nullCheckTypes;
int nullCount;
Scope associatedScope;
public LoopingFlowContext(
FlowContext parent,
FlowInfo upstreamNullFlowInfo,
ASTNode associatedNode,
BranchLabel breakLabel,
BranchLabel continueLabel,
Scope associatedScope) {
super(parent, associatedNode, breakLabel);
this.preemptNullDiagnostic = true;
// children will defer to this, which may defer to its own parent
this.continueLabel = continueLabel;
this.associatedScope = associatedScope;
this.upstreamNullFlowInfo = upstreamNullFlowInfo.unconditionalCopy();
}
/**
* Perform deferred checks relative to final variables duplicate initialization
* of lack of initialization.
* @param scope the scope to which this context is associated
* @param flowInfo the flow info against which checks must be performed
*/
public void complainOnDeferredFinalChecks(BlockScope scope, FlowInfo flowInfo) {
// complain on final assignments in loops
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) {
if (flowInfo.isPotentiallyAssigned((FieldBinding) variable)) {
complained = true;
scope.problemReporter().duplicateInitializationOfBlankFinalField(
(FieldBinding) variable,
this.finalAssignments[i]);
}
} else {
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 context = this.parent;
while (context != null) {
context.removeFinalAssignmentIfAny(this.finalAssignments[i]);
context = context.parent;
}
}
}
}
/**
* Perform deferred checks relative to the null status of local variables.
* @param scope the scope to which this context is associated
* @param callerFlowInfo the flow info against which checks must be performed
*/
public void complainOnDeferredNullChecks(BlockScope scope, FlowInfo callerFlowInfo) {
for (int i = 0 ; i < this.innerFlowContextsCount ; i++) {
this.upstreamNullFlowInfo.
addPotentialNullInfoFrom(
this.innerFlowContexts[i].upstreamNullFlowInfo).
addPotentialNullInfoFrom(this.innerFlowInfos[i]);
}
this.innerFlowContextsCount = 0;
UnconditionalFlowInfo flowInfo = this.upstreamNullFlowInfo.
addPotentialNullInfoFrom(callerFlowInfo.unconditionalInitsWithoutSideEffect());
if (this.deferNullDiagnostic) {
// check only immutable null checks on innermost looping context
for (int i = 0; i < this.nullCount; i++) {
LocalVariableBinding local = this.nullLocals[i];
Expression expression = this.nullReferences[i];
// final local variable
switch (this.nullCheckTypes[i]) {
case CAN_ONLY_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
this.nullReferences[i] = null;
if (this.nullCheckTypes[i] == (CAN_ONLY_NON_NULL | IN_COMPARISON_NON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, expression);
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, expression);
}
continue;
}
break;
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
this.nullReferences[i] = null;
if (this.nullCheckTypes[i] == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, expression);
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, expression);
}
continue;
}
if (flowInfo.isDefinitelyNull(local)) {
this.nullReferences[i] = null;
if (this.nullCheckTypes[i] == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNull(local, expression);
} else {
scope.problemReporter().localVariableNullComparedToNonNull(local, expression);
}
continue;
}
break;
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:
if (flowInfo.isDefinitelyNull(local)) {
this.nullReferences[i] = null;
switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
scope.problemReporter().localVariableRedundantCheckOnNull(local, expression);
continue;
case FlowContext.IN_COMPARISON_NON_NULL:
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;
}
}
break;
case MAY_NULL:
if (flowInfo.isDefinitelyNull(local)) {
this.nullReferences[i] = null;
scope.problemReporter().localVariableNullReference(local, expression);
continue;
}
break;
default:
// never happens
}
this.parent.recordUsingNullReference(scope, local, expression,
this.nullCheckTypes[i], flowInfo);
}
}
else {
// check inconsistent null checks on outermost looping context
for (int i = 0; i < this.nullCount; i++) {
Expression expression = this.nullReferences[i];
// final local variable
LocalVariableBinding local = this.nullLocals[i];
switch (this.nullCheckTypes[i]) {
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
this.nullReferences[i] = null;
if (this.nullCheckTypes[i] == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, expression);
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, expression);
}
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:
if (flowInfo.isDefinitelyNull(local)) {
this.nullReferences[i] = null;
switch(this.nullCheckTypes[i] & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
scope.problemReporter().localVariableRedundantCheckOnNull(local, expression);
continue;
case FlowContext.IN_COMPARISON_NON_NULL:
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;
}
}
break;
case MAY_NULL:
if (flowInfo.isDefinitelyNull(local)) {
this.nullReferences[i] = null;
scope.problemReporter().localVariableNullReference(local, expression);
continue;
}
if (flowInfo.isPotentiallyNull(local)) {
this.nullReferences[i] = null;
scope.problemReporter().localVariablePotentialNullReference(local, expression);
continue;
}
break;
default:
// never happens
}
}
}
// propagate breaks
this.initsOnBreak.addPotentialNullInfoFrom(flowInfo);
for (int i = 0; i < this.breakTargetsCount; i++) {
this.breakTargetContexts[i].initsOnBreak.addPotentialNullInfoFrom(flowInfo);
}
}
public BranchLabel continueLabel() {
return this.continueLabel;
}
public String individualToString() {
StringBuffer buffer = new StringBuffer("Looping flow context"); //$NON-NLS-1$
buffer.append("[initsOnBreak - ").append(this.initsOnBreak.toString()).append(']'); //$NON-NLS-1$
buffer.append("[initsOnContinue - ").append(this.initsOnContinue.toString()).append(']'); //$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 isContinuable() {
return true;
}
public boolean isContinuedTo() {
return this.initsOnContinue != FlowInfo.DEAD_END;
}
public void recordBreakTo(FlowContext targetContext) {
if (targetContext instanceof LabelFlowContext) {
int current;
if ((current = this.breakTargetsCount++) == 0) {
this.breakTargetContexts = new LabelFlowContext[2];
} else if (current == this.breakTargetContexts.length) {
System.arraycopy(this.breakTargetContexts, 0, this.breakTargetContexts = new LabelFlowContext[current + 2], 0, current);
}
this.breakTargetContexts[current] = (LabelFlowContext) targetContext;
}
}
public void recordContinueFrom(FlowContext innerFlowContext, FlowInfo flowInfo) {
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) {
if ((this.initsOnContinue.tagBits & FlowInfo.UNREACHABLE) == 0) {
this.initsOnContinue = this.initsOnContinue.
mergedWith(flowInfo.unconditionalInitsWithoutSideEffect());
}
else {
this.initsOnContinue = flowInfo.unconditionalCopy();
}
FlowContext inner = innerFlowContext;
while (inner != this && !(inner instanceof LoopingFlowContext)) {
inner = inner.parent;
}
if (inner == this) {
this.upstreamNullFlowInfo.
addPotentialNullInfoFrom(
flowInfo.unconditionalInitsWithoutSideEffect());
}
else {
int length = 0;
if (this.innerFlowContexts == null) {
this.innerFlowContexts = new LoopingFlowContext[5];
this.innerFlowInfos = new UnconditionalFlowInfo[5];
}
else if (this.innerFlowContextsCount ==
(length = this.innerFlowContexts.length) - 1) {
System.arraycopy(this.innerFlowContexts, 0,
(this.innerFlowContexts = new LoopingFlowContext[length + 5]),
0, length);
System.arraycopy(this.innerFlowInfos, 0,
(this.innerFlowInfos= new UnconditionalFlowInfo[length + 5]),
0, length);
}
this.innerFlowContexts[this.innerFlowContextsCount] = (LoopingFlowContext) inner;
this.innerFlowInfos[this.innerFlowContextsCount++] =
flowInfo.unconditionalInitsWithoutSideEffect();
}
}
}
protected boolean recordFinalAssignment(
VariableBinding binding,
Reference finalAssignment) {
// do not consider variables which are defined inside this loop
if (binding instanceof LocalVariableBinding) {
Scope scope = ((LocalVariableBinding) binding).declaringScope;
while ((scope = scope.parent) != null) {
if (scope == this.associatedScope)
return false;
}
}
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;
}
protected void recordNullReference(LocalVariableBinding local,
Expression expression, int status) {
if (this.nullCount == 0) {
this.nullLocals = new LocalVariableBinding[5];
this.nullReferences = new Expression[5];
this.nullCheckTypes = new int[5];
}
else if (this.nullCount == this.nullLocals.length) {
System.arraycopy(this.nullLocals, 0,
this.nullLocals = new LocalVariableBinding[this.nullCount * 2], 0, this.nullCount);
System.arraycopy(this.nullReferences, 0,
this.nullReferences = new Expression[this.nullCount * 2], 0, this.nullCount);
System.arraycopy(this.nullCheckTypes, 0,
this.nullCheckTypes = new int[this.nullCount * 2], 0, this.nullCount);
}
this.nullLocals[this.nullCount] = local;
this.nullReferences[this.nullCount] = expression;
this.nullCheckTypes[this.nullCount++] = status;
}
public void recordUsingNullReference(Scope scope, LocalVariableBinding local,
Expression reference, int checkType, FlowInfo flowInfo) {
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) != 0 ||
flowInfo.isDefinitelyUnknown(local)) {
return;
}
switch (checkType) {
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
if (flowInfo.isDefinitelyNonNull(local)) {
if (checkType == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNonNull(local, reference);
} else {
scope.problemReporter().localVariableNonNullComparedToNull(local, reference);
}
} else if (flowInfo.isDefinitelyNull(local)) {
if (checkType == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL)) {
scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
} else {
scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
}
} else if (! flowInfo.cannotBeDefinitelyNullOrNonNull(local)) {
if (flowInfo.isPotentiallyNonNull(local)) {
recordNullReference(local, reference, CAN_ONLY_NON_NULL | checkType & CONTEXT_MASK);
} else {
recordNullReference(local, reference, checkType);
}
}
return;
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:
if (flowInfo.isPotentiallyNonNull(local)
|| flowInfo.isPotentiallyUnknown(local)) {
return;
}
if (flowInfo.isDefinitelyNull(local)) {
switch(checkType & CONTEXT_MASK) {
case FlowContext.IN_COMPARISON_NULL:
scope.problemReporter().localVariableRedundantCheckOnNull(local, reference);
return;
case FlowContext.IN_COMPARISON_NON_NULL:
scope.problemReporter().localVariableNullComparedToNonNull(local, reference);
return;
case FlowContext.IN_ASSIGNMENT:
scope.problemReporter().localVariableRedundantNullAssignment(local, reference);
return;
case FlowContext.IN_INSTANCEOF:
scope.problemReporter().localVariableNullInstanceof(local, reference);
return;
}
}
recordNullReference(local, reference, checkType);
return;
case MAY_NULL :
if (flowInfo.isDefinitelyNonNull(local)) {
return;
}
if (flowInfo.isDefinitelyNull(local)) {
scope.problemReporter().localVariableNullReference(local, reference);
return;
}
if (flowInfo.isPotentiallyNull(local)) {
scope.problemReporter().localVariablePotentialNullReference(local, reference);
return;
}
recordNullReference(local, reference, checkType);
return;
default:
// never happens
}
}
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;
}
}
}
}