Java tutorial
/******************************************************************************* * 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; } }