/******************************************************************************* * 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 * Jesper S Moller - Contributions for * bug 404657 - [1.8][compiler] Analysis for effectively final variables fails to consider loops *******************************************************************************/ 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.TagBits; 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)) { variable.tagBits &= ~TagBits.IsEffectivelyFinal; if (variable.isFinal()) { 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; } }