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

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 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 <stephan@cs.tu-berlin.de> - Contributions for 
 *                          bug 292478 - Report potentially null across variable assignment
 *                          bug 332637 - Dead Code detection removing code that isn't dead
 *                        bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional
 *                        Bug 411964 - [1.8][null] leverage null type annotation in foreach statement
 *                        Bug 421035 - [resource] False alarm of resource leak warning when casting a closeable in its assignment
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;

public abstract class FlowInfo {

    public int tagBits; // REACHABLE by default
    public final static int REACHABLE = 0;
    /* unreachable code 
     * eg. while (true);
     *     i++;  --> unreachable code 
     */
    public final static int UNREACHABLE_OR_DEAD = 1;
    /* unreachable code as inferred by null analysis
     * eg. str = null;
     *     if (str != null) {
     *        // dead code
     *     }
     */
    public final static int UNREACHABLE_BY_NULLANALYSIS = 2;
    /*
     * code unreachable in any fashion
     */
    public final static int UNREACHABLE = UNREACHABLE_OR_DEAD | UNREACHABLE_BY_NULLANALYSIS;
    public final static int NULL_FLAG_MASK = 4;

    public final static int UNKNOWN = 1;
    public final static int NULL = 2;
    public final static int NON_NULL = 4;
    public final static int POTENTIALLY_UNKNOWN = 8;
    public final static int POTENTIALLY_NULL = 16;
    public final static int POTENTIALLY_NON_NULL = 32;

    public final static int UNROOTED = 64; // marks a flowInfo that may be appended to another flowInfo (accepting incoming nulls/nonnulls, see UFI.iNBit/iNNBit).

    public static final int FREE_TYPEVARIABLE = FlowInfo.POTENTIALLY_NULL | FlowInfo.POTENTIALLY_NON_NULL;

    public static final UnconditionalFlowInfo DEAD_END; // Represents a dead branch status of initialization
    static {
        DEAD_END = new UnconditionalFlowInfo();
        DEAD_END.tagBits = UNREACHABLE;
    }

    /**
     * Add other inits to this flow info, then return this. The operation semantics
     * are to match as closely as possible the application to this flow info of all
     * the operations that resulted into otherInits.
     * @param otherInits other inits to add to this
     * @return this, modified according to otherInits information
     */
    abstract public FlowInfo addInitializationsFrom(FlowInfo otherInits);

    /**
     * Add all null information from otherInits to this flow info and return this.
     * The operation models the effect of an unconditional sequence of this flow info
     * and otherInits.
     */
    abstract public FlowInfo addNullInfoFrom(FlowInfo otherInits);

    /**
     * Compose other inits over this flow info, then return this. The operation
     * semantics are to wave into this flow info the consequences of a possible
     * path into the operations that resulted into otherInits. The fact that this
     * path may be left unexecuted under peculiar conditions results into less
     * specific results than {@link #addInitializationsFrom(FlowInfo)
     * addInitializationsFrom}.
     * @param otherInits other inits to compose over this
     * @return this, modified according to otherInits information
     */
    abstract public FlowInfo addPotentialInitializationsFrom(FlowInfo otherInits);

    public FlowInfo asNegatedCondition() {

        return this;
    }

    public static FlowInfo conditional(FlowInfo initsWhenTrue, FlowInfo initsWhenFalse) {
        if (initsWhenTrue == initsWhenFalse)
            return initsWhenTrue;
        // if (initsWhenTrue.equals(initsWhenFalse)) return initsWhenTrue; -- could optimize if #equals is defined
        return new ConditionalFlowInfo(initsWhenTrue, initsWhenFalse);
    }

    /**
     * Check whether a given local variable is known to be unable to gain a definite
     * non null or definite null status by the use of an enclosing flow info. The
     * semantics are that if the current flow info marks the variable as potentially
     * unknown or else as being both potentially null and potentially non null,
     * then it won't ever be promoted as definitely null or definitely non null. (It
     * could still get promoted to definite unknown).
     * @param local the variable to check
     * @return true iff this flow info prevents local from being promoted to
     *         definite non null or definite null against an enclosing flow info
     */
    public boolean cannotBeDefinitelyNullOrNonNull(LocalVariableBinding local) {
        return isPotentiallyUnknown(local) || isPotentiallyNonNull(local) && isPotentiallyNull(local);
    }

    /**
     * Check whether a given local variable is known to be non null, either because
     * it is definitely non null, or because is has been tested against non null.
     * @param local the variable to ckeck
     * @return true iff local cannot be null for this flow info
     */
    public boolean cannotBeNull(LocalVariableBinding local) {
        return isDefinitelyNonNull(local) || isProtectedNonNull(local);
    }

    /**
     * Check whether a given local variable is known to be null, either because it
     * is definitely null, or because is has been tested against null.
     * @param local the variable to ckeck
     * @return true iff local can only be null for this flow info
     */
    public boolean canOnlyBeNull(LocalVariableBinding local) {
        return isDefinitelyNull(local) || isProtectedNull(local);
    }

    /**
     * Return a deep copy of the current instance.
     * @return a deep copy of this flow info
     */
    abstract public FlowInfo copy();

    public static UnconditionalFlowInfo initial(int maxFieldCount) {
        UnconditionalFlowInfo info = new UnconditionalFlowInfo();
        info.maxFieldCount = maxFieldCount;
        return info;
    }

    /**
     * Return the flow info that would result from the path associated to the
     * value false for the condition expression that generated this flow info.
     * May be this flow info if it is not an instance of {@link
     * ConditionalFlowInfo}. May have a side effect on subparts of this flow
     * info (subtrees get merged).
     * @return the flow info associated to the false branch of the condition
     *          that generated this flow info
     */
    abstract public FlowInfo initsWhenFalse();

    /**
     * Return the flow info that would result from the path associated to the
     * value true for the condition expression that generated this flow info.
     * May be this flow info if it is not an instance of {@link
     * ConditionalFlowInfo}. May have a side effect on subparts of this flow
     * info (subtrees get merged).
     * @return the flow info associated to the true branch of the condition
     *          that generated this flow info
     */
    abstract public FlowInfo initsWhenTrue();

    /**
     * Check status of definite assignment for a field.
     */
    abstract public boolean isDefinitelyAssigned(FieldBinding field);

    /**
     * Check status of definite assignment for a local.
     */
    public abstract boolean isDefinitelyAssigned(LocalVariableBinding local);

    /**
     * Check status of definite non-null value for a given local variable.
     * @param local the variable to ckeck
     * @return true iff local is definitely non null for this flow info
     */
    public abstract boolean isDefinitelyNonNull(LocalVariableBinding local);

    /**
     * Check status of definite null value for a given local variable.
     * @param local the variable to ckeck
     * @return true iff local is definitely null for this flow info
     */
    public abstract boolean isDefinitelyNull(LocalVariableBinding local);

    /**
     * Check status of definite unknown value for a given local variable.
     * @param local the variable to ckeck
     * @return true iff local is definitely unknown for this flow info
     */
    public abstract boolean isDefinitelyUnknown(LocalVariableBinding local);

    /**
     * Check if any null info has been recorded for a given local variable.
     * Here even recording of 'UNKNOWN' is considered as null info.
     */
    public abstract boolean hasNullInfoFor(LocalVariableBinding local);

    /**
     * Check status of potential assignment for a field.
     */
    abstract public boolean isPotentiallyAssigned(FieldBinding field);

    /**
     * Check status of potential assignment for a local variable.
     */

    abstract public boolean isPotentiallyAssigned(LocalVariableBinding field);

    /**
     * Check status of potential null assignment for a local. Return true if there
     * is a reasonable expectation that the variable be non null at this point.
     * @param local LocalVariableBinding - the binding for the checked local
     * @return true if there is a reasonable expectation that local be non null at
     * this point
     */
    public abstract boolean isPotentiallyNonNull(LocalVariableBinding local);

    /**
     * Check status of potential null assignment for a local. Return true if there
     * is a reasonable expectation that the variable be null at this point. This
     * includes the protected null case, so as to augment diagnostics, but does not
     * really check that someone deliberately assigned to null on any specific
     * path
     * @param local LocalVariableBinding - the binding for the checked local
     * @return true if there is a reasonable expectation that local be null at
     * this point
     */
    public abstract boolean isPotentiallyNull(LocalVariableBinding local);

    /**
     * Return true if the given local may have been assigned to an unknown value.
     * @param local the local to check
     * @return true if the given local may have been assigned to an unknown value
     */
    public abstract boolean isPotentiallyUnknown(LocalVariableBinding local);

    /**
     * Return true if the given local is protected by a test against a non null
     * value.
     * @param local the local to check
     * @return true if the given local is protected by a test against a non null
     */
    public abstract boolean isProtectedNonNull(LocalVariableBinding local);

    /**
     * Return true if the given local is protected by a test against null.
     * @param local the local to check
     * @return true if the given local is protected by a test against null
     */
    public abstract boolean isProtectedNull(LocalVariableBinding local);

    /**
     * Record that a local variable got checked to be non null.
     * @param local the checked local variable
     */
    abstract public void markAsComparedEqualToNonNull(LocalVariableBinding local);

    /**
     * Record that a local variable got checked to be null.
     * @param local the checked local variable
     */
    abstract public void markAsComparedEqualToNull(LocalVariableBinding local);

    /**
     * Record a field got definitely assigned.
     */
    abstract public void markAsDefinitelyAssigned(FieldBinding field);

    /**
     * Record a local got definitely assigned to a non-null value.
     */
    abstract public void markAsDefinitelyNonNull(LocalVariableBinding local);

    /**
     * Record a local got definitely assigned to null.
     */
    abstract public void markAsDefinitelyNull(LocalVariableBinding local);

    /**
     * Reset all null-information about a given local.
     */
    abstract public void resetNullInfo(LocalVariableBinding local);

    /**
     * Record a local may have got assigned to unknown (set the bit on existing info).
     */
    abstract public void markPotentiallyUnknownBit(LocalVariableBinding local);

    /**
     * Record a local may have got assigned to null (set the bit on existing info).
     */
    abstract public void markPotentiallyNullBit(LocalVariableBinding local);

    /**
     * Record a local may have got assigned to non-null (set the bit on existing info).
     */
    abstract public void markPotentiallyNonNullBit(LocalVariableBinding local);

    /**
     * Record a local got definitely assigned.
     */
    abstract public void markAsDefinitelyAssigned(LocalVariableBinding local);

    /**
     * Record a local got definitely assigned to an unknown value.
     */
    abstract public void markAsDefinitelyUnknown(LocalVariableBinding local);

    /**
     * Mark the null status of the given local according to the given status
     * @param local
     * @param nullStatus bitset of FLowInfo.UNKNOWN ... FlowInfo.POTENTIALLY_NON_NULL
     */
    public void markNullStatus(LocalVariableBinding local, int nullStatus) {
        switch (nullStatus) {
        // definite status?
        case FlowInfo.UNKNOWN:
            markAsDefinitelyUnknown(local);
            break;
        case FlowInfo.NULL:
            markAsDefinitelyNull(local);
            break;
        case FlowInfo.NON_NULL:
            markAsDefinitelyNonNull(local);
            break;
        default:
            // collect potential status:
            resetNullInfo(local);
            if ((nullStatus & FlowInfo.POTENTIALLY_UNKNOWN) != 0)
                markPotentiallyUnknownBit(local);
            if ((nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
                markPotentiallyNullBit(local);
            if ((nullStatus & FlowInfo.POTENTIALLY_NON_NULL) != 0)
                markPotentiallyNonNullBit(local);
            if ((nullStatus & (FlowInfo.POTENTIALLY_NULL | FlowInfo.POTENTIALLY_NON_NULL
                    | FlowInfo.POTENTIALLY_UNKNOWN)) == 0)
                markAsDefinitelyUnknown(local);
        }
    }

    /**
     * Answer the null status of the given local
     * @param local
     * @return bitset of FlowInfo.UNKNOWN ... FlowInfo.POTENTIALLY_NON_NULL
     */
    public int nullStatus(LocalVariableBinding local) {
        if (isDefinitelyUnknown(local))
            return FlowInfo.UNKNOWN;
        if (isDefinitelyNull(local))
            return FlowInfo.NULL;
        if (isDefinitelyNonNull(local))
            return FlowInfo.NON_NULL;
        int status = 0;
        if (isPotentiallyUnknown(local))
            status |= FlowInfo.POTENTIALLY_UNKNOWN;
        if (isPotentiallyNull(local))
            status |= FlowInfo.POTENTIALLY_NULL;
        if (isPotentiallyNonNull(local))
            status |= FlowInfo.POTENTIALLY_NON_NULL;
        if (status > 0)
            return status;
        return FlowInfo.UNKNOWN;
    }

    /**
     * Merge two single bits (NULL, NON_NULL, POTENTIALLY*..) into one.
     * This method implements a simpler logic than the 4-bit encoding used in FlowInfo instances.
     */
    public static int mergeNullStatus(int nullStatus1, int nullStatus2) {
        boolean canBeNull = false;
        boolean canBeNonNull = false;
        switch (nullStatus1) {
        case POTENTIALLY_NULL:
            canBeNonNull = true;
            //$FALL-THROUGH$
        case NULL:
            canBeNull = true;
            break;
        case POTENTIALLY_NON_NULL:
            canBeNull = true;
            //$FALL-THROUGH$
        case NON_NULL:
            canBeNonNull = true;
            break;
        }
        switch (nullStatus2) {
        case POTENTIALLY_NULL:
            canBeNonNull = true;
            //$FALL-THROUGH$
        case NULL:
            canBeNull = true;
            break;
        case POTENTIALLY_NON_NULL:
            canBeNull = true;
            //$FALL-THROUGH$
        case NON_NULL:
            canBeNonNull = true;
            break;
        }
        if (canBeNull) {
            if (canBeNonNull)
                return POTENTIALLY_NULL;
            else
                return NULL;
        } else {
            if (canBeNonNull)
                return NON_NULL;
            else
                return UNKNOWN;
        }
    }

    /**
     * Merge branches using optimized boolean conditions
     */
    public static UnconditionalFlowInfo mergedOptimizedBranches(FlowInfo initsWhenTrue, boolean isOptimizedTrue,
            FlowInfo initsWhenFalse, boolean isOptimizedFalse, boolean allowFakeDeadBranch) {
        UnconditionalFlowInfo mergedInfo;
        if (isOptimizedTrue) {
            if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) {
                mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).unconditionalInits();
            } else {
                mergedInfo = initsWhenTrue
                        .addPotentialInitializationsFrom(initsWhenFalse.nullInfoLessUnconditionalCopy())
                        .unconditionalInits();
            }
        } else if (isOptimizedFalse) {
            if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) {
                mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).unconditionalInits();
            } else {
                mergedInfo = initsWhenFalse
                        .addPotentialInitializationsFrom(initsWhenTrue.nullInfoLessUnconditionalCopy())
                        .unconditionalInits();
            }
        } else {
            mergedInfo = initsWhenTrue.mergedWith(initsWhenFalse.unconditionalInits());
        }
        return mergedInfo;
    }

    /**
     * Merge if-else branches using optimized boolean conditions
     */
    public static UnconditionalFlowInfo mergedOptimizedBranchesIfElse(FlowInfo initsWhenTrue,
            boolean isOptimizedTrue, FlowInfo initsWhenFalse, boolean isOptimizedFalse, boolean allowFakeDeadBranch,
            FlowInfo flowInfo, IfStatement ifStatement, boolean reportDeadCodeInKnownPattern) {
        UnconditionalFlowInfo mergedInfo;
        if (isOptimizedTrue) {
            if (initsWhenTrue == FlowInfo.DEAD_END && allowFakeDeadBranch) {
                if (!reportDeadCodeInKnownPattern) {
                    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=256796
                    // do not report code even after if-else as dead as a consequence of analysis done in known dead code pattern
                    // when the CompilerOptions$reportDeadCodeInTrivialIfStatement option is disabled
                    if (ifStatement.elseStatement == null) {
                        mergedInfo = flowInfo.unconditionalInits();
                    } else {
                        mergedInfo = initsWhenFalse.unconditionalInits();
                        if (initsWhenFalse != FlowInfo.DEAD_END) {
                            // let the definitely true status of known dead code pattern not affect the reachability
                            mergedInfo.setReachMode(flowInfo.reachMode());
                        }
                    }
                } else {
                    mergedInfo = initsWhenFalse.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).unconditionalInits();
                }
            } else {
                mergedInfo = initsWhenTrue
                        .addPotentialInitializationsFrom(initsWhenFalse.nullInfoLessUnconditionalCopy())
                        .unconditionalInits();
            }
        } else if (isOptimizedFalse) {
            if (initsWhenFalse == FlowInfo.DEAD_END && allowFakeDeadBranch) {
                if (!reportDeadCodeInKnownPattern) {
                    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=256796
                    // do not report code even after if-else as dead as a consequence of analysis done in known dead code pattern
                    // when the CompilerOptions$reportDeadCodeInTrivialIfStatement option is disabled
                    if (ifStatement.thenStatement == null) {
                        mergedInfo = flowInfo.unconditionalInits();
                    } else {
                        mergedInfo = initsWhenTrue.unconditionalInits();
                        if (initsWhenTrue != FlowInfo.DEAD_END) {
                            // let the definitely false status of known dead code pattern not affect the reachability
                            mergedInfo.setReachMode(flowInfo.reachMode());
                        }
                    }
                } else {
                    mergedInfo = initsWhenTrue.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD).unconditionalInits();
                }
            } else {
                mergedInfo = initsWhenFalse
                        .addPotentialInitializationsFrom(initsWhenTrue.nullInfoLessUnconditionalCopy())
                        .unconditionalInits();
            }
        } else if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0
                && (ifStatement.bits & ASTNode.IsElseStatementUnreachable) != 0
                && initsWhenTrue != FlowInfo.DEAD_END && initsWhenFalse != FlowInfo.DEAD_END) {
            // Done when the then branch will always be executed but the condition does not have a boolean
            // true or false (i.e if(true), etc) for sure
            // We don't do this if both if and else branches themselves are in an unreachable code
            // or if any of them is a DEAD_END (e.g. contains 'return' or 'throws')
            mergedInfo = initsWhenTrue
                    .addPotentialInitializationsFrom(initsWhenFalse.nullInfoLessUnconditionalCopy())
                    .unconditionalInits();
            // if a variable is only initialized in one branch and not initialized in the other,
            // then we need to cast a doubt on its initialization in the merged info
            mergedInfo.mergeDefiniteInitsWith(initsWhenFalse.unconditionalCopy());

            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=415997, classify unreachability precisely, IsElseStatementUnreachable could be due to null analysis
            if ((mergedInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0
                    && (initsWhenFalse.tagBits & FlowInfo.UNREACHABLE) == FlowInfo.UNREACHABLE_BY_NULLANALYSIS) {
                mergedInfo.tagBits &= ~UNREACHABLE_OR_DEAD;
                mergedInfo.tagBits |= UNREACHABLE_BY_NULLANALYSIS;
            }
        } else if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0
                && (ifStatement.bits & ASTNode.IsThenStatementUnreachable) != 0
                && initsWhenTrue != FlowInfo.DEAD_END && initsWhenFalse != FlowInfo.DEAD_END) {
            // Done when the else branch will always be executed but the condition does not have a boolean
            // true or false (i.e if(true), etc) for sure
            // We don't do this if both if and else branches themselves are in an unreachable code
            // or if any of them is a DEAD_END (e.g. contains 'return' or 'throws')
            mergedInfo = initsWhenFalse
                    .addPotentialInitializationsFrom(initsWhenTrue.nullInfoLessUnconditionalCopy())
                    .unconditionalInits();
            // if a variable is only initialized in one branch and not initialized in the other,
            // then we need to cast a doubt on its initialization in the merged info
            mergedInfo.mergeDefiniteInitsWith(initsWhenTrue.unconditionalCopy());
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=415997, classify unreachability precisely, IsThenStatementUnreachable could be due to null analysis
            if ((mergedInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0
                    && (initsWhenTrue.tagBits & FlowInfo.UNREACHABLE) == FlowInfo.UNREACHABLE_BY_NULLANALYSIS) {
                mergedInfo.tagBits &= ~UNREACHABLE_OR_DEAD;
                mergedInfo.tagBits |= UNREACHABLE_BY_NULLANALYSIS;
            }
        } else {
            mergedInfo = initsWhenTrue.mergedWith(initsWhenFalse.unconditionalInits());
        }
        return mergedInfo;
    }

    /**
     * Find out the reachability mode of this flowInfo.
     * @return REACHABLE if this flow info is reachable, otherwise
     *         either UNREACHABLE_OR_DEAD or UNREACHABLE_BY_NULLANALYSIS.
     */
    public int reachMode() {
        return this.tagBits & UNREACHABLE;
    }

    /**
     * Return a flow info that carries the same information as the result of
     * {@link #initsWhenTrue() initsWhenTrue}, but warrantied to be different
     * from this.<br>
     * Caveat: side effects on the result may affect components of this.
     * @return the result of initsWhenTrue or a copy of it
     */
    abstract public FlowInfo safeInitsWhenTrue();

    /**
     * Set this flow info reach mode and return this.
     * @param reachMode one of {@link #REACHABLE REACHABLE}, {@link #UNREACHABLE_OR_DEAD UNREACHABLE_OR_DEAD},
     * {@link #UNREACHABLE_BY_NULLANALYSIS UNREACHABLE_BY_NULLANALYSIS} or {@link #UNREACHABLE UNREACHABLE}
     * @return this, with the reach mode set to reachMode
     */
    abstract public FlowInfo setReachMode(int reachMode);

    /**
     * Return the intersection of this and otherInits, that is
     * one of:<ul>
     *   <li>the receiver updated in the following way:<ul>
     *     <li>intersection of definitely assigned variables,
     *     <li>union of potentially assigned variables,
     *     <li>similar operations for null,</ul>
     *   <li>or the receiver or otherInits if the other one is non
     *       reachable.</ul>
     * otherInits is not affected, and is not returned either (no
     * need to protect the result).
     * @param otherInits the flow info to merge with this
     * @return the intersection of this and otherInits.
     */
    abstract public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits);

    abstract public UnconditionalFlowInfo mergeDefiniteInitsWith(UnconditionalFlowInfo otherInits);

    /**
     * Return a copy of this unconditional flow info, deprived from its null
     * info. {@link #DEAD_END DEAD_END} is returned unmodified.
     * @return a copy of this unconditional flow info deprived from its null info
     */
    abstract public UnconditionalFlowInfo nullInfoLessUnconditionalCopy();

    @Override
    public String toString() {

        if (this == DEAD_END) {
            return "FlowInfo.DEAD_END"; //$NON-NLS-1$
        }
        return super.toString();
    }

    /**
     * Return a new flow info that holds the same information as this would after
     * a call to unconditionalInits, but leaving this info unaffected. Moreover,
     * the result can be modified without affecting this.
     * @return a new flow info carrying this unconditional flow info
     */
    abstract public UnconditionalFlowInfo unconditionalCopy();

    /**
     * Return a new flow info that holds the same information as this would after
     * a call to {@link #unconditionalInits() unconditionalInits} followed by the
     * erasure of fields specific information, but leaving this flow info unaffected.
     * @return a new flow info carrying the unconditional flow info for local variables
     */
    abstract public UnconditionalFlowInfo unconditionalFieldLessCopy();

    /**
     * Return a flow info that merges the possible paths of execution described by
     * this flow info. In case of an unconditional flow info, return this. In case
     * of a conditional flow info, merge branches recursively. Caveat: this may
     * be affected, and modifying the result may affect this.
     * @return a flow info that merges the possible paths of execution described by
     *          this
     */
    abstract public UnconditionalFlowInfo unconditionalInits();

    /**
     * Return a new flow info that holds the same information as this would after
     * a call to {@link #unconditionalInits() unconditionalInits}, but leaving
     * this info unaffected. Side effects on the result might affect this though
     * (consider it as read only).
     * @return a flow info carrying this unconditional flow info
     */
    abstract public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect();

    /**
     * Resets the definite and potential initialization info for the given local variable
     * @param local
     */
    abstract public void resetAssignmentInfo(LocalVariableBinding local);

    /**
     * Check whether 'tagBits' contains either {@link TagBits#AnnotationNonNull} or {@link TagBits#AnnotationNullable},
     * and answer the corresponding null status ({@link #NON_NULL} etc.).
     */
    public static int tagBitsToNullStatus(long tagBits) {
        if ((tagBits & TagBits.AnnotationNonNull) != 0)
            return NON_NULL;
        if ((tagBits & TagBits.AnnotationNullable) != 0)
            return POTENTIALLY_NULL | FlowInfo.POTENTIALLY_UNKNOWN;
        return UNKNOWN;
    }
}