org.eclipse.jdt.internal.compiler.flow.FlowContext.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.compiler.flow.FlowContext.java

Source

/*******************************************************************************
 * 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...
 */
@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.
     */
    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 
     */
    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.
     *
     * @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. */
    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 {@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 {@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);
    }

    /**
     * @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.
     */
    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. 
     */
    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.
     * @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.
     * @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
     */
    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.
     * @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.
     * @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.
     * @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. */
    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).
     * @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.
     * @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
    }
}