Copyright (c) 2000, 2017 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 358827 - [1.7] exception analysis for t-w-r spoils null analysis bug 186342 - [compiler][null] Using annotations for null checking 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 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" bug 383368 - [compiler][null] syntactic null analysis for field references bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check bug 403086 - [compiler][null] include the effect of 'assert' in syntactic null analysis for fields bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking Bug 453483 - [compiler][null][loop] Improve null analysis for loops Bug 455723 - Nonnull argument not correctly inferred in loop Bug 415790 - [compiler][resource]Incorrect potential resource leak warning in for loop with close in try/catch Bug 446691 - [1.8][null][compiler] NullPointerException in SingleNameReference.analyseCode
/******************************************************************************* * Copyright (c) 2000, 2017 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 358827 - [1.7] exception analysis for t-w-r spoils null analysis * bug 186342 - [compiler][null] Using annotations for null checking * 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 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" * bug 383368 - [compiler][null] syntactic null analysis for field references * bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check * bug 403086 - [compiler][null] include the effect of 'assert' in syntactic null analysis for fields * bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking * Bug 453483 - [compiler][null][loop] Improve null analysis for loops * Bug 455723 - Nonnull argument not correctly inferred in loop * Bug 415790 - [compiler][resource]Incorrect potential resource leak warning in for loop with close in try/catch * Bug 446691 - [1.8][null][compiler] NullPointerException in SingleNameReference.analyseCode *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow; import java.util.ArrayList; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FakedTrackingVariable; import org.eclipse.jdt.internal.compiler.ast.LabeledStatement; import org.eclipse.jdt.internal.compiler.ast.LambdaExpression; import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching; import org.eclipse.jdt.internal.compiler.ast.Reference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement; import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 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...
/** * Reflects the context of code analysis, keeping track of enclosing * try statements, exception handlers, etc... */
@SuppressWarnings({"rawtypes", "unchecked"}) public class FlowContext implements TypeConstants { // preempt marks looping contexts public final static FlowContext NotContinuableContext = new FlowContext(null, null, true); public ASTNode associatedNode; public FlowContext parent; public FlowInfo initsOnFinally; // only used within try blocks; remembers upstream flow info mergedWith // any null related operation happening within the try block
Used to record whether effects in a try block affect the finally-block conditionally or unconditionally. -1 means: no effect, 0 means: unconditional effect, > 0 means levels of nested conditional structures.
/** * Used to record whether effects in a try block affect the finally-block * conditionally or unconditionally. * -1 means: no effect, * 0 means: unconditional effect, * > 0 means levels of nested conditional structures. */
public int conditionalLevel = -1; public int tagBits; // array to store the provided and expected types from the potential error location (for display in error messages): public TypeBinding[][] providedExpectedTypes = null; // record field references known to be non-null // this array will never shrink, only grow. reset happens by nulling expired entries // this array grows in lock step with timesToLiveForNullCheckInfo, which controls expiration private Reference[] nullCheckedFieldReferences = null; private int[] timesToLiveForNullCheckInfo = null; public static final int DEFER_NULL_DIAGNOSTIC = 0x1; public static final int PREEMPT_NULL_DIAGNOSTIC = 0x2; // inside an assertFalse or a not-expression checks for equality / inequality have reversed meaning for syntactic analysis for fields: public static final int INSIDE_NEGATION = 0x4;
used to hide null comparison related warnings inside assert statements
/** * used to hide null comparison related warnings inside assert statements */
public static final int HIDE_NULL_COMPARISON_WARNING = 0x1000; public static final int HIDE_NULL_COMPARISON_WARNING_MASK = 0xF000; public static final int CAN_ONLY_NULL_NON_NULL = 0x0000; //check against null and non null, with definite values -- comparisons public static final int CAN_ONLY_NULL = 0x0001; //check against null, with definite values -- comparisons public static final int CAN_ONLY_NON_NULL = 0x0002; //check against non null, with definite values -- comparisons public static final int MAY_NULL = 0x0003; //check binding a value to a @NonNull variable public final static int ASSIGN_TO_NONNULL = 0x0080; //check against an unboxing conversion public static final int IN_UNBOXING = 0x0010; //check against unclosed resource at early exit: public static final int EXIT_RESOURCE = 0x0800; // check against null, with potential values -- NPE guard public static final int CHECK_MASK = 0x00FF; public static final int IN_COMPARISON_NULL = 0x0100; public static final int IN_COMPARISON_NON_NULL = 0x0200; // check happened in a comparison public static final int IN_ASSIGNMENT = 0x0300; // check happened in an assignment public static final int IN_INSTANCEOF = 0x0400; // check happened in an instanceof expression public static final int CONTEXT_MASK = ~CHECK_MASK & ~HIDE_NULL_COMPARISON_WARNING_MASK; public FlowContext(FlowContext parent, ASTNode associatedNode, boolean inheritNullFieldChecks) { this.parent = parent; this.associatedNode = associatedNode; if (parent != null) { if ((parent.tagBits & (FlowContext.DEFER_NULL_DIAGNOSTIC | FlowContext.PREEMPT_NULL_DIAGNOSTIC)) != 0) { this.tagBits |= FlowContext.DEFER_NULL_DIAGNOSTIC; } this.initsOnFinally = parent.initsOnFinally; this.conditionalLevel = parent.conditionalLevel; if (inheritNullFieldChecks) copyNullCheckedFieldsFrom(parent); // re-use list if there is one } } public void copyNullCheckedFieldsFrom(FlowContext other) { Reference[] fieldReferences = other.nullCheckedFieldReferences; if (fieldReferences != null && fieldReferences.length > 0 && fieldReferences[0] != null) { this.nullCheckedFieldReferences = other.nullCheckedFieldReferences; this.timesToLiveForNullCheckInfo = other.timesToLiveForNullCheckInfo; } }
Record that a reference to a field has been seen in a non-null state.
Params:
  • reference – Can be a SingleNameReference, a FieldReference or a QualifiedNameReference resolving to a field
  • timeToLive – control how many expire events are needed to expire this information
/** * Record that a reference to a field has been seen in a non-null state. * * @param reference Can be a SingleNameReference, a FieldReference or a QualifiedNameReference resolving to a field * @param timeToLive control how many expire events are needed to expire this information */
public void recordNullCheckedFieldReference(Reference reference, int timeToLive) { if (this.nullCheckedFieldReferences == null) { // first entry: this.nullCheckedFieldReferences = new Reference[] { reference, null }; this.timesToLiveForNullCheckInfo = new int[] { timeToLive, -1 }; } else { int len = this.nullCheckedFieldReferences.length; // insert into first empty slot: for (int i=0; i<len; i++) { if (this.nullCheckedFieldReferences[i] == null) { this.nullCheckedFieldReferences[i] = reference; this.timesToLiveForNullCheckInfo[i] = timeToLive; return; } } // grow arrays: System.arraycopy(this.nullCheckedFieldReferences, 0, this.nullCheckedFieldReferences=new Reference[len+2], 0, len); System.arraycopy(this.timesToLiveForNullCheckInfo, 0, this.timesToLiveForNullCheckInfo=new int[len+2], 0, len); this.nullCheckedFieldReferences[len] = reference; this.timesToLiveForNullCheckInfo[len] = timeToLive; } }
If a null checked field has been recorded recently, increase its time to live.
/** If a null checked field has been recorded recently, increase its time to live. */
public void extendTimeToLiveForNullCheckedField(int t) { if (this.timesToLiveForNullCheckInfo != null) { for (int i = 0; i < this.timesToLiveForNullCheckInfo.length; i++) if (this.timesToLiveForNullCheckInfo[i] > 0) this.timesToLiveForNullCheckInfo[i] += t; } }
Forget any information about fields that were previously known to be non-null. Will only cause any effect if CompilerOptions.enableSyntacticNullAnalysisForFields (implicitly by guards before calls to recordNullCheckedFieldReference(Reference, int)).
/** * Forget any information about fields that were previously known to be non-null. * * Will only cause any effect if CompilerOptions.enableSyntacticNullAnalysisForFields * (implicitly by guards before calls to {@link #recordNullCheckedFieldReference(Reference, int)}). */
public void expireNullCheckedFieldInfo() { if (this.nullCheckedFieldReferences != null) { for (int i = 0; i < this.nullCheckedFieldReferences.length; i++) { if (--this.timesToLiveForNullCheckInfo[i] == 0) this.nullCheckedFieldReferences[i] = null; } } }
Is the given field reference equivalent to a reference that is freshly known to be non-null? Can only return true if CompilerOptions.enableSyntacticNullAnalysisForFields (implicitly by guards before calls to recordNullCheckedFieldReference(Reference, int)).
/** * Is the given field reference equivalent to a reference that is freshly known to be non-null? * Can only return true if CompilerOptions.enableSyntacticNullAnalysisForFields * (implicitly by guards before calls to {@link #recordNullCheckedFieldReference(Reference, int)}). */
public boolean isNullcheckedFieldAccess(Reference reference) { if (this.nullCheckedFieldReferences == null) // always null unless CompilerOptions.enableSyntacticNullAnalysisForFields return false; int len = this.nullCheckedFieldReferences.length; for (int i=0; i<len; i++) { Reference checked = this.nullCheckedFieldReferences[i]; if (checked == null) { continue; } if (checked.isEquivalent(reference)) { return true; } } return false; } public BranchLabel breakLabel() { return null; } public void checkExceptionHandlers(TypeBinding raisedException, ASTNode location, FlowInfo flowInfo, BlockScope scope) { checkExceptionHandlers(raisedException, location, flowInfo, scope, false); }
Params:
  • isExceptionOnAutoClose – This is for checking exception handlers for exceptions raised during the auto close of resources inside a try with resources statement. (Relevant for source levels 1.7 and above only)
/** * @param isExceptionOnAutoClose This is for checking exception handlers for exceptions raised during the * auto close of resources inside a try with resources statement. (Relevant for * source levels 1.7 and above only) */
public void checkExceptionHandlers(TypeBinding raisedException, ASTNode location, FlowInfo flowInfo, BlockScope scope, boolean isExceptionOnAutoClose) { // LIGHT-VERSION OF THE EQUIVALENT WITH AN ARRAY OF EXCEPTIONS // check that all the argument exception types are handled // JDK Compatible implementation - when an exception type is thrown, // all related catch blocks are marked as reachable... instead of those only // until the point where it is safely handled (Smarter - see comment at the end) FlowContext traversedContext = this; ArrayList abruptlyExitedLoops = null; if (scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_7 && location instanceof ThrowStatement) { Expression throwExpression = ((ThrowStatement)location).exception; LocalVariableBinding throwArgBinding = throwExpression.localVariableBinding(); if (throwExpression instanceof SingleNameReference // https://bugs.eclipse.org/bugs/show_bug.cgi?id=350361 && throwArgBinding instanceof CatchParameterBinding && throwArgBinding.isEffectivelyFinal()) { CatchParameterBinding parameter = (CatchParameterBinding) throwArgBinding; checkExceptionHandlers(parameter.getPreciseTypes(), location, flowInfo, scope); return; } } while (traversedContext != null) { SubRoutineStatement sub; if (((sub = traversedContext.subroutine()) != null) && sub.isSubRoutineEscaping()) { // traversing a non-returning subroutine means that all unhandled // exceptions will actually never get sent... return; } // filter exceptions that are locally caught from the innermost enclosing // try statement to the outermost ones. if (traversedContext instanceof ExceptionHandlingFlowContext) { ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext; ReferenceBinding[] caughtExceptions; if ((caughtExceptions = exceptionContext.handledExceptions) != Binding.NO_EXCEPTIONS) { boolean definitelyCaught = false; for (int caughtIndex = 0, caughtCount = caughtExceptions.length; caughtIndex < caughtCount; caughtIndex++) { ReferenceBinding caughtException = caughtExceptions[caughtIndex]; FlowInfo exceptionFlow = flowInfo; int state = caughtException == null ? Scope.EQUAL_OR_MORE_SPECIFIC /* any exception */ : Scope.compareTypes(raisedException, caughtException); if (abruptlyExitedLoops != null && caughtException != null && state != Scope.NOT_RELATED) { for (int i = 0, abruptlyExitedLoopsCount = abruptlyExitedLoops.size(); i < abruptlyExitedLoopsCount; i++) { LoopingFlowContext loop = (LoopingFlowContext) abruptlyExitedLoops.get(i); loop.recordCatchContextOfEscapingException(exceptionContext, caughtException, flowInfo); } exceptionFlow = FlowInfo.DEAD_END; // don't use flow info on first round, flow info will be evaluated during loopback simulation } switch (state) { case Scope.EQUAL_OR_MORE_SPECIFIC : exceptionContext.recordHandlingException( caughtException, exceptionFlow.unconditionalInits(), raisedException, raisedException, // precise exception that will be caught location, definitelyCaught); // was it already definitely caught ? definitelyCaught = true; break; case Scope.MORE_GENERIC : exceptionContext.recordHandlingException( caughtException, exceptionFlow.unconditionalInits(), raisedException, caughtException, location, false); // was not caught already per construction } } if (definitelyCaught) return; } // method treatment for unchecked exceptions if (exceptionContext.isMethodContext) { if (raisedException.isUncheckedException(false)) return; boolean shouldMergeUnhandledExceptions = exceptionContext instanceof ExceptionInferenceFlowContext; // anonymous constructors are allowed to throw any exceptions (their thrown exceptions // clause will be fixed up later as per JLS 8.6). if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) { AbstractMethodDeclaration method = (AbstractMethodDeclaration)exceptionContext.associatedNode; if (method.isConstructor() && method.binding.declaringClass.isAnonymousType()) shouldMergeUnhandledExceptions = true; } if (shouldMergeUnhandledExceptions) { exceptionContext.mergeUnhandledException(raisedException); return; // no need to complain, will fix up constructor/lambda exceptions } break; // not handled anywhere, thus jump to error handling } } else if (traversedContext instanceof LoopingFlowContext) { if (abruptlyExitedLoops == null) { abruptlyExitedLoops = new ArrayList(5); } abruptlyExitedLoops.add(traversedContext); } traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); if (!isExceptionOnAutoClose) { if (traversedContext instanceof InsideSubRoutineFlowContext) { ASTNode node = traversedContext.associatedNode; if (node instanceof TryStatement) { TryStatement tryStatement = (TryStatement) node; flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); // collect inits } } } traversedContext = traversedContext.getLocalParent(); } // if reaches this point, then there are some remaining unhandled exception types. if (isExceptionOnAutoClose) { scope.problemReporter().unhandledExceptionFromAutoClose(raisedException, location); } else { scope.problemReporter().unhandledException(raisedException, location); } } public void checkExceptionHandlers(TypeBinding[] raisedExceptions, ASTNode location, FlowInfo flowInfo, BlockScope scope) { // check that all the argument exception types are handled // JDK Compatible implementation - when an exception type is thrown, // all related catch blocks are marked as reachable... instead of those only // until the point where it is safely handled (Smarter - see comment at the end) int remainingCount; // counting the number of remaining unhandled exceptions int raisedCount; // total number of exceptions raised if ((raisedExceptions == null) || ((raisedCount = raisedExceptions.length) == 0)) return; remainingCount = raisedCount; // duplicate the array of raised exceptions since it will be updated // (null replaces any handled exception) System.arraycopy( raisedExceptions, 0, (raisedExceptions = new TypeBinding[raisedCount]), 0, raisedCount); FlowContext traversedContext = this; ArrayList abruptlyExitedLoops = null; while (traversedContext != null) { SubRoutineStatement sub; if (((sub = traversedContext.subroutine()) != null) && sub.isSubRoutineEscaping()) { // traversing a non-returning subroutine means that all unhandled // exceptions will actually never get sent... return; } // filter exceptions that are locally caught from the innermost enclosing // try statement to the outermost ones. if (traversedContext instanceof ExceptionHandlingFlowContext) { ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext; ReferenceBinding[] caughtExceptions; if ((caughtExceptions = exceptionContext.handledExceptions) != Binding.NO_EXCEPTIONS) { int caughtCount = caughtExceptions.length; boolean[] locallyCaught = new boolean[raisedCount]; // at most for (int caughtIndex = 0; caughtIndex < caughtCount; caughtIndex++) { ReferenceBinding caughtException = caughtExceptions[caughtIndex]; for (int raisedIndex = 0; raisedIndex < raisedCount; raisedIndex++) { TypeBinding raisedException; if ((raisedException = raisedExceptions[raisedIndex]) != null) { FlowInfo exceptionFlow = flowInfo; int state = caughtException == null ? Scope.EQUAL_OR_MORE_SPECIFIC /* any exception */ : Scope.compareTypes(raisedException, caughtException); if (abruptlyExitedLoops != null && caughtException != null && state != Scope.NOT_RELATED) { for (int i = 0, abruptlyExitedLoopsCount = abruptlyExitedLoops.size(); i < abruptlyExitedLoopsCount; i++) { LoopingFlowContext loop = (LoopingFlowContext) abruptlyExitedLoops.get(i); loop.recordCatchContextOfEscapingException(exceptionContext, caughtException, flowInfo); } exceptionFlow = FlowInfo.DEAD_END; // don't use flow info on first round, flow info will be evaluated during loopback simulation } switch (state) { case Scope.EQUAL_OR_MORE_SPECIFIC : exceptionContext.recordHandlingException( caughtException, exceptionFlow.unconditionalInits(), raisedException, raisedException, // precise exception that will be caught location, locallyCaught[raisedIndex]); // was already definitely caught ? if (!locallyCaught[raisedIndex]) { locallyCaught[raisedIndex] = true; // remember that this exception has been definitely caught remainingCount--; } break; case Scope.MORE_GENERIC : exceptionContext.recordHandlingException( caughtException, exceptionFlow.unconditionalInits(), raisedException, caughtException, location, false); // was not caught already per construction } } } } // remove locally caught exceptions from the remaining ones for (int i = 0; i < raisedCount; i++) { if (locallyCaught[i]) { raisedExceptions[i] = null; // removed from the remaining ones. } } } // method treatment for unchecked exceptions if (exceptionContext.isMethodContext) { for (int i = 0; i < raisedCount; i++) { TypeBinding raisedException; if ((raisedException = raisedExceptions[i]) != null) { if (raisedException.isUncheckedException(false)) { remainingCount--; raisedExceptions[i] = null; } } } boolean shouldMergeUnhandledException = exceptionContext instanceof ExceptionInferenceFlowContext; // anonymous constructors are allowed to throw any exceptions (their thrown exceptions // clause will be fixed up later as per JLS 8.6). if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) { AbstractMethodDeclaration method = (AbstractMethodDeclaration)exceptionContext.associatedNode; if (method.isConstructor() && method.binding.declaringClass.isAnonymousType()) shouldMergeUnhandledException = true; } if (shouldMergeUnhandledException) { for (int i = 0; i < raisedCount; i++) { TypeBinding raisedException; if ((raisedException = raisedExceptions[i]) != null) { exceptionContext.mergeUnhandledException(raisedException); } } return; // no need to complain, will fix up constructor/lambda exceptions } break; // not handled anywhere, thus jump to error handling } } else if (traversedContext instanceof LoopingFlowContext) { if (abruptlyExitedLoops == null) { abruptlyExitedLoops = new ArrayList(5); } abruptlyExitedLoops.add(traversedContext); } if (remainingCount == 0) return; traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); if (traversedContext instanceof InsideSubRoutineFlowContext) { ASTNode node = traversedContext.associatedNode; if (node instanceof TryStatement) { TryStatement tryStatement = (TryStatement) node; flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); // collect inits } } traversedContext = traversedContext.getLocalParent(); } // if reaches this point, then there are some remaining unhandled exception types. nextReport: for (int i = 0; i < raisedCount; i++) { TypeBinding exception; if ((exception = raisedExceptions[i]) != null) { // only one complaint if same exception declared to be thrown more than once for (int j = 0; j < i; j++) { if (TypeBinding.equalsEquals(raisedExceptions[j], exception)) continue nextReport; // already reported } scope.problemReporter().unhandledException(exception, location); } } } public BranchLabel continueLabel() { return null; } public FlowInfo getInitsForFinalBlankInitializationCheck(TypeBinding declaringType, FlowInfo flowInfo) { FlowContext current = this; FlowInfo inits = flowInfo; do { if (current instanceof InitializationFlowContext) { InitializationFlowContext initializationContext = (InitializationFlowContext) current; if (TypeBinding.equalsEquals(((TypeDeclaration)initializationContext.associatedNode).binding, declaringType)) { return inits; } inits = initializationContext.initsBeforeContext; current = initializationContext.initializationParent; } else if (current instanceof ExceptionHandlingFlowContext) { if(current instanceof FieldInitsFakingFlowContext) { return FlowInfo.DEAD_END; // isDefinitelyAssigned will return true for all fields } ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) current; current = exceptionContext.initializationParent == null ? exceptionContext.parent : exceptionContext.initializationParent; } else { current = current.getLocalParent(); } } while (current != null); // not found throw new IllegalStateException(declaringType.debugName()); } /* * lookup through break labels */ public FlowContext getTargetContextForBreakLabel(char[] labelName) { FlowContext current = this, lastNonReturningSubRoutine = null; while (current != null) { if (current.isNonReturningContext()) { lastNonReturningSubRoutine = current; } char[] currentLabelName; if (((currentLabelName = current.labelName()) != null) && CharOperation.equals(currentLabelName, labelName)) { ((LabeledStatement)current.associatedNode).bits |= ASTNode.LabelUsed; if (lastNonReturningSubRoutine == null) return current; return lastNonReturningSubRoutine; } current = current.getLocalParent(); } // not found return null; } /* * lookup through continue labels */ public FlowContext getTargetContextForContinueLabel(char[] labelName) { FlowContext current = this; FlowContext lastContinuable = null; FlowContext lastNonReturningSubRoutine = null; while (current != null) { if (current.isNonReturningContext()) { lastNonReturningSubRoutine = current; } else { if (current.isContinuable()) { lastContinuable = current; } } char[] currentLabelName; if ((currentLabelName = current.labelName()) != null && CharOperation.equals(currentLabelName, labelName)) { ((LabeledStatement)current.associatedNode).bits |= ASTNode.LabelUsed; // matching label found if ((lastContinuable != null) && (current.associatedNode.concreteStatement() == lastContinuable.associatedNode)) { if (lastNonReturningSubRoutine == null) return lastContinuable; return lastNonReturningSubRoutine; } // label is found, but not a continuable location return FlowContext.NotContinuableContext; } current = current.getLocalParent(); } // not found return null; } /* * lookup a default break through breakable locations */ public FlowContext getTargetContextForDefaultBreak() { FlowContext current = this, lastNonReturningSubRoutine = null; while (current != null) { if (current.isNonReturningContext()) { lastNonReturningSubRoutine = current; } if (current.isBreakable() && current.labelName() == null) { if (lastNonReturningSubRoutine == null) return current; return lastNonReturningSubRoutine; } current = current.getLocalParent(); } // not found return null; } /* * lookup a default yield through switch expression locations */ public FlowContext getTargetContextForDefaultYield() { FlowContext current = this, lastNonReturningSubRoutine = null; while (current != null) { if (current.isNonReturningContext()) { lastNonReturningSubRoutine = current; } if (current.isBreakable() && current.labelName() == null && ((SwitchFlowContext) current).isExpression){ if (lastNonReturningSubRoutine == null) return current; return lastNonReturningSubRoutine; } current = current.getLocalParent(); } // not found return null; } /* * lookup a default continue amongst continuable locations */ public FlowContext getTargetContextForDefaultContinue() { FlowContext current = this, lastNonReturningSubRoutine = null; while (current != null) { if (current.isNonReturningContext()) { lastNonReturningSubRoutine = current; } if (current.isContinuable()) { if (lastNonReturningSubRoutine == null) return current; return lastNonReturningSubRoutine; } current = current.getLocalParent(); } // not found return null; }
Answer flow context that corresponds to initialization. Suitably override in subtypes.
/** * Answer flow context that corresponds to initialization. Suitably override in subtypes. */
public FlowContext getInitializationContext() { return null; }
Answer the parent flow context but be careful not to cross the boundary of a nested type, or null if no such parent exists.
/** * Answer the parent flow context but be careful not to cross the boundary of a nested type, * or null if no such parent exists. */
public FlowContext getLocalParent() { if (this.associatedNode instanceof AbstractMethodDeclaration || this.associatedNode instanceof TypeDeclaration || this.associatedNode instanceof LambdaExpression) return null; return this.parent; } public String individualToString() { return "Flow context"; //$NON-NLS-1$ } public FlowInfo initsOnBreak() { return FlowInfo.DEAD_END; } public UnconditionalFlowInfo initsOnReturn() { return FlowInfo.DEAD_END; } public boolean isBreakable() { return false; } public boolean isContinuable() { return false; } public boolean isNonReturningContext() { return false; } public boolean isSubRoutine() { return false; } public char[] labelName() { return null; }
Record a given null status of a given local variable as it will be seen in the finally block.
Params:
  • local – the local variable being observed
  • nullStatus – the null status of local at the current point in the flow
/** * Record a given null status of a given local variable as it will be seen in the finally block. * @param local the local variable being observed * @param nullStatus the null status of local at the current point in the flow */
public void markFinallyNullStatus(LocalVariableBinding local, int nullStatus) { if (this.initsOnFinally == null) return; if (this.conditionalLevel == -1) return; if (this.conditionalLevel == 0) { // node is unconditionally reached, take nullStatus as is: this.initsOnFinally.markNullStatus(local, nullStatus); return; } // node is reached only conditionally, weaken status to potentially_ and merge with previous UnconditionalFlowInfo newInfo = this.initsOnFinally.unconditionalCopy(); newInfo.markNullStatus(local, nullStatus); this.initsOnFinally = this.initsOnFinally.mergedWith(newInfo); }
Merge the effect of a statement presumably contained in a try-block, i.e., record how the collected info will affect the corresponding finally-block. Precondition: caller has checked that initsOnFinally != null.
Params:
  • flowInfo – info after executing a statement of the try-block.
/** * Merge the effect of a statement presumably contained in a try-block, * i.e., record how the collected info will affect the corresponding finally-block. * Precondition: caller has checked that initsOnFinally != null. * @param flowInfo info after executing a statement of the try-block. */
public void mergeFinallyNullInfo(FlowInfo flowInfo) { if (this.initsOnFinally == null) return; if (this.conditionalLevel == -1) return; if (this.conditionalLevel == 0) { // node is unconditionally reached, take null info as is: this.initsOnFinally.addNullInfoFrom(flowInfo); return; } // node is reached only conditionally: merge flowInfo with existing since both paths are possible this.initsOnFinally = this.initsOnFinally.mergedWith(flowInfo.unconditionalCopy()); }
Record the fact that an abrupt exit has been observed, one of: - potential exception (incl. unchecked exceptions) - break - continue - return
/** * Record the fact that an abrupt exit has been observed, one of: * - potential exception (incl. unchecked exceptions) * - break * - continue * - return */
public void recordAbruptExit() { if (this.conditionalLevel > -1) { this.conditionalLevel++; // delegate up up-to the enclosing try-finally: if (!(this instanceof ExceptionHandlingFlowContext) && this.parent != null) { this.parent.recordAbruptExit(); } } } public void recordBreakFrom(FlowInfo flowInfo) { // default implementation: do nothing } public void recordBreakTo(FlowContext targetContext) { // default implementation: do nothing } public void recordContinueFrom(FlowContext innerFlowContext, FlowInfo flowInfo) { // default implementation: do nothing }
Record that we found an early exit from a method while a resource is in scope.
Params:
  • scope – enclosing scope
  • flowInfo – flowInfo at the point of the early exit
  • trackingVar – representation of the resource
  • reference – the return or throw statement marking the early exit
Returns:true if the situation has been handled by this flow context.
/** * Record that we found an early exit from a method while a resource is in scope. * @param scope enclosing scope * @param flowInfo flowInfo at the point of the early exit * @param trackingVar representation of the resource * @param reference the return or throw statement marking the early exit * @return true if the situation has been handled by this flow context. */
public boolean recordExitAgainstResource(BlockScope scope, FlowInfo flowInfo, FakedTrackingVariable trackingVar, ASTNode reference) { return false; // not handled } protected void recordProvidedExpectedTypes(TypeBinding providedType, TypeBinding expectedType, int nullCount) { if (nullCount == 0) { this.providedExpectedTypes = new TypeBinding[5][]; } else if (this.providedExpectedTypes == null) { int size = 5; while (size <= nullCount) size *= 2; this.providedExpectedTypes = new TypeBinding[size][]; } else if (nullCount >= this.providedExpectedTypes.length) { int oldLen = this.providedExpectedTypes.length; System.arraycopy(this.providedExpectedTypes, 0, this.providedExpectedTypes = new TypeBinding[nullCount * 2][], 0, oldLen); } this.providedExpectedTypes[nullCount] = new TypeBinding[]{providedType, expectedType}; } protected boolean recordFinalAssignment(VariableBinding variable, Reference finalReference) { return true; // keep going }
Record a null reference for use by deferred checks. Only looping or finally contexts really record that information. Other contexts immediately check for unboxing.
Params:
/** * Record a null reference for use by deferred checks. Only looping or * finally contexts really record that information. Other contexts * immediately check for unboxing. * @param local the local variable involved in the check * @param location the location triggering the analysis, for normal null dereference * this is an expression resolving to 'local', for resource leaks it is an * early exit statement. * @param checkType the checkType against which the check must be performed; one of * {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL}, * {@link #CAN_ONLY_NON_NULL CAN_ONLY_NON_NULL}, potentially * combined with a context indicator (one of {@link #IN_COMPARISON_NULL}, * {@link #IN_COMPARISON_NON_NULL}, {@link #IN_ASSIGNMENT} or {@link #IN_INSTANCEOF}). * <br> * Alternatively, a {@link #IN_UNBOXING} check can e requested. * @param nullInfo the null flow info observed at this first visit of location. */
protected void recordNullReference(LocalVariableBinding local, ASTNode location, int checkType, FlowInfo nullInfo) { // default implementation: do nothing }
Either AST analysis or checking of a child flow context has encountered an unboxing situation. Record this fact for handling at an appropriate point in time.
Params:
  • nullStatus – the status as we know it so far.
/** * Either AST analysis or checking of a child flow context has encountered an unboxing situation. * Record this fact for handling at an appropriate point in time. * @param nullStatus the status as we know it so far. */
public void recordUnboxing(Scope scope, Expression expression, int nullStatus, FlowInfo flowInfo) { // default: handle immediately: checkUnboxing(scope, expression, flowInfo); }
During deferred checking re-visit a previously recording unboxing situation.
/** During deferred checking re-visit a previously recording unboxing situation. */
protected void checkUnboxing(Scope scope, Expression expression, FlowInfo flowInfo) { int status = expression.nullStatus(flowInfo, this); if ((status & FlowInfo.NULL) != 0) { scope.problemReporter().nullUnboxing(expression, expression.resolvedType); return; } else if ((status & FlowInfo.POTENTIALLY_NULL) != 0) { scope.problemReporter().potentialNullUnboxing(expression, expression.resolvedType); return; } else if ((status & FlowInfo.NON_NULL) != 0) { return; } // not handled, perhaps our parent will eventually have something to say? if (this.parent != null) { this.parent.recordUnboxing(scope, expression, FlowInfo.UNKNOWN, flowInfo); } } public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { // default implementation: do nothing } public void recordSettingFinal(VariableBinding variable, Reference finalReference, FlowInfo flowInfo) { if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) { // for initialization inside looping statement that effectively loops FlowContext context = this; while (context != null) { if (!context.recordFinalAssignment(variable, finalReference)) { break; // no need to keep going } context = context.getLocalParent(); } } }
Record a null reference for use by deferred checks. Only looping or finally contexts really record that information. The context may emit an error immediately depending on the status of local against flowInfo and its nature (only looping of finally contexts defer part of the checks; nonetheless, contexts that are nested into a looping or a finally context get affected and delegate some checks to their enclosing context).
Params:
  • scope – the scope into which the check is performed
  • local – the local variable involved in the check
  • location – the location triggering the analysis, for normal null dereference this is an expression resolving to 'local', for resource leaks it is an early exit statement.
  • checkType – the status against which the check must be performed; one of CAN_ONLY_NULL, CAN_ONLY_NULL_NON_NULL, MAY_NULL, potentially combined with a context indicator (one of IN_COMPARISON_NULL, IN_COMPARISON_NON_NULL, IN_ASSIGNMENT or IN_INSTANCEOF) and a bit to indicate whether the reference is being recorded inside an assert, HIDE_NULL_COMPARISON_WARNING
  • flowInfo – the flow info at the check point; deferring contexts will perform supplementary checks against flow info instances that cannot be known at the time of calling this method (they are influenced by code that follows the current point)
/** * Record a null reference for use by deferred checks. Only looping or * finally contexts really record that information. The context may * emit an error immediately depending on the status of local against * flowInfo and its nature (only looping of finally contexts defer part * of the checks; nonetheless, contexts that are nested into a looping or a * finally context get affected and delegate some checks to their enclosing * context). * @param scope the scope into which the check is performed * @param local the local variable involved in the check * @param location the location triggering the analysis, for normal null dereference * this is an expression resolving to 'local', for resource leaks it is an * early exit statement. * @param checkType the status against which the check must be performed; one * of {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL}, potentially * combined with a context indicator (one of {@link #IN_COMPARISON_NULL}, * {@link #IN_COMPARISON_NON_NULL}, {@link #IN_ASSIGNMENT} or {@link #IN_INSTANCEOF}) * and a bit to indicate whether the reference is being recorded inside an assert, * {@link #HIDE_NULL_COMPARISON_WARNING} * @param flowInfo the flow info at the check point; deferring contexts will * perform supplementary checks against flow info instances that cannot * be known at the time of calling this method (they are influenced by * code that follows the current point) */
public void recordUsingNullReference(Scope scope, LocalVariableBinding local, ASTNode location, int checkType, FlowInfo flowInfo) { if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) != 0 || flowInfo.isDefinitelyUnknown(local)) { return; } // 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; 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; } else if (flowInfo.cannotBeDefinitelyNullOrNonNull(local)) { 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; } } else if (flowInfo.cannotBeDefinitelyNullOrNonNull(local)) { return; } break; case MAY_NULL : if (flowInfo.isDefinitelyNull(local)) { scope.problemReporter().localVariableNullReference(local, location); return; } if (flowInfo.isPotentiallyNull(local)) { if(local.type.isFreeTypeVariable()) { scope.problemReporter().localVariableFreeTypeVariableReference(local, location); return; } scope.problemReporter().localVariablePotentialNullReference(local, location); return; } break; default: // never happens } if (this.parent != null) { this.parent.recordUsingNullReference(scope, local, location, checkType, flowInfo); } } void removeFinalAssignmentIfAny(Reference reference) { // default implementation: do nothing } public SubRoutineStatement subroutine() { return null; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); FlowContext current = this; int parentsCount = 0; while ((current = current.parent) != null) { parentsCount++; } FlowContext[] parents = new FlowContext[parentsCount + 1]; current = this; int index = parentsCount; while (index >= 0) { parents[index--] = current; current = current.parent; } for (int i = 0; i < parentsCount; i++) { for (int j = 0; j < i; j++) buffer.append('\t'); buffer.append(parents[i].individualToString()).append('\n'); } buffer.append('*'); for (int j = 0; j < parentsCount + 1; j++) buffer.append('\t'); buffer.append(individualToString()).append('\n'); return buffer.toString(); }
Record that a nullity mismatch was detected against an annotated type reference.
Params:
  • currentScope – scope for error reporting
  • expression – the expression violating the specification
  • providedType – the type of the provided value, i.e., either expression or an element thereof (in ForeachStatements)
  • expectedType – the declared type of the spec'ed variable, for error reporting.
  • flowInfo – the flowInfo observed when visiting expression
  • nullStatus – the null status of expression at the current location
  • annotationStatus – status from type annotation analysis, or null
/** * Record that a nullity mismatch was detected against an annotated type reference. * @param currentScope scope for error reporting * @param expression the expression violating the specification * @param providedType the type of the provided value, i.e., either expression or an element thereof (in ForeachStatements) * @param expectedType the declared type of the spec'ed variable, for error reporting. * @param flowInfo the flowInfo observed when visiting expression * @param nullStatus the null status of expression at the current location * @param annotationStatus status from type annotation analysis, or null */
public void recordNullityMismatch(BlockScope currentScope, Expression expression, TypeBinding providedType, TypeBinding expectedType, FlowInfo flowInfo, int nullStatus, NullAnnotationMatching annotationStatus) { if (providedType == null) { return; // assume type error was already reported } if (expression.localVariableBinding() != null) { // flowContext cannot yet handle non-localvar expressions (e.g., fields) // find the inner-most flowContext that might need deferred handling: FlowContext currentContext = this; while (currentContext != null) { // some flow contexts implement deferred checking, should we participate in that? int isInsideAssert = 0x0; if ((this.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) { isInsideAssert = FlowContext.HIDE_NULL_COMPARISON_WARNING; } if (currentContext.internalRecordNullityMismatch(expression, providedType, flowInfo, nullStatus, expectedType, ASSIGN_TO_NONNULL | isInsideAssert)) return; currentContext = currentContext.parent; } } // no reason to defer, so report now: if (annotationStatus != null) currentScope.problemReporter().nullityMismatchingTypeAnnotation(expression, providedType, expectedType, annotationStatus); else currentScope.problemReporter().nullityMismatch(expression, providedType, expectedType, nullStatus, currentScope.environment().getNonNullAnnotationName()); } protected boolean internalRecordNullityMismatch(Expression expression, TypeBinding providedType, FlowInfo flowInfo, int nullStatus, TypeBinding expectedType, int checkType) { // nop, to be overridden in subclasses return false; // not recorded } }