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 - Contribution for bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check Bug 453483 - [compiler][null][loop] Improve null analysis for loops Bug 421035 - [resource] False alarm of resource leak warning when casting a closeable in its assignment
/******************************************************************************* * 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 - Contribution for * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" * bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check * Bug 453483 - [compiler][null][loop] Improve null analysis for loops * Bug 421035 - [resource] False alarm of resource leak warning when casting a closeable in its assignment *******************************************************************************/
package org.eclipse.jdt.internal.compiler.flow; import java.util.ArrayList; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference; import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.codegen.ObjectCache; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding; import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; 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.TypeIds;
Reflects the context of code analysis, keeping track of enclosing try statements, exception handlers, etc...
/** * Reflects the context of code analysis, keeping track of enclosing * try statements, exception handlers, etc... */
@SuppressWarnings({"rawtypes", "unchecked"}) public class ExceptionHandlingFlowContext extends FlowContext { public final static int BitCacheSize = 32; // 32 bits per int public ReferenceBinding[] handledExceptions; int[] isReached; int[] isNeeded; // WARNING: This is an array that maps to catch blocks, not caught exceptions (which could be more than catch blocks in a multi-catch block) UnconditionalFlowInfo[] initsOnExceptions; ObjectCache indexes = new ObjectCache(); boolean isMethodContext; public UnconditionalFlowInfo initsOnReturn; public FlowContext initializationParent; // special parent relationship only for initialization purpose // for dealing with anonymous constructor thrown exceptions public ArrayList extendedExceptions; private static final Argument[] NO_ARGUMENTS = new Argument[0]; public Argument [] catchArguments; private int[] exceptionToCatchBlockMap; public ExceptionHandlingFlowContext( FlowContext parent, ASTNode associatedNode, ReferenceBinding[] handledExceptions, FlowContext initializationParent, BlockScope scope, UnconditionalFlowInfo flowInfo) { this(parent, associatedNode, handledExceptions, null, NO_ARGUMENTS, initializationParent, scope, flowInfo); } public ExceptionHandlingFlowContext( FlowContext parent, TryStatement tryStatement, ReferenceBinding[] handledExceptions, int [] exceptionToCatchBlockMap, FlowContext initializationParent, BlockScope scope, FlowInfo flowInfo) { this(parent, tryStatement, handledExceptions, exceptionToCatchBlockMap, tryStatement.catchArguments, initializationParent, scope, flowInfo.unconditionalInits()); UnconditionalFlowInfo unconditionalCopy = flowInfo.unconditionalCopy(); unconditionalCopy.iNBit = -1L; unconditionalCopy.iNNBit = -1L; unconditionalCopy.tagBits |= FlowInfo.UNROOTED; this.initsOnFinally = unconditionalCopy; } ExceptionHandlingFlowContext( FlowContext parent, ASTNode associatedNode, ReferenceBinding[] handledExceptions, int [] exceptionToCatchBlockMap, Argument [] catchArguments, FlowContext initializationParent, BlockScope scope, UnconditionalFlowInfo flowInfo) { super(parent, associatedNode, true); this.isMethodContext = scope == scope.methodScope(); this.handledExceptions = handledExceptions; this.catchArguments = catchArguments; this.exceptionToCatchBlockMap = exceptionToCatchBlockMap; int count = handledExceptions.length, cacheSize = (count / ExceptionHandlingFlowContext.BitCacheSize) + 1; this.isReached = new int[cacheSize]; // none is reached by default this.isNeeded = new int[cacheSize]; // none is needed by default this.initsOnExceptions = new UnconditionalFlowInfo[count]; boolean markExceptionsAndThrowableAsReached = !this.isMethodContext || scope.compilerOptions().reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable; for (int i = 0; i < count; i++) { ReferenceBinding handledException = handledExceptions[i]; int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[i] : i; this.indexes.put(handledException, i); // key type -> value index if (handledException.isUncheckedException(true)) { if (markExceptionsAndThrowableAsReached || handledException.id != TypeIds.T_JavaLangThrowable && handledException.id != TypeIds.T_JavaLangException) { this.isReached[i / ExceptionHandlingFlowContext.BitCacheSize] |= 1 << (i % ExceptionHandlingFlowContext.BitCacheSize); } this.initsOnExceptions[catchBlock] = flowInfo.unconditionalCopy(); } else { this.initsOnExceptions[catchBlock] = FlowInfo.DEAD_END; } } if (!this.isMethodContext) { System.arraycopy(this.isReached, 0, this.isNeeded, 0, cacheSize); } this.initsOnReturn = FlowInfo.DEAD_END; this.initializationParent = initializationParent; } public void complainIfUnusedExceptionHandlers(AbstractMethodDeclaration method) { MethodScope scope = method.scope; // can optionally skip overriding methods if ((method.binding.modifiers & (ExtraCompilerModifiers.AccOverriding | ExtraCompilerModifiers.AccImplementing)) != 0 && !scope.compilerOptions().reportUnusedDeclaredThrownExceptionWhenOverriding) { return; } // report errors for unreachable exception handlers TypeBinding[] docCommentReferences = null; int docCommentReferencesLength = 0; if (scope.compilerOptions(). reportUnusedDeclaredThrownExceptionIncludeDocCommentReference && method.javadoc != null && method.javadoc.exceptionReferences != null && (docCommentReferencesLength = method.javadoc.exceptionReferences.length) > 0) { docCommentReferences = new TypeBinding[docCommentReferencesLength]; for (int i = 0; i < docCommentReferencesLength; i++) { docCommentReferences[i] = method.javadoc.exceptionReferences[i].resolvedType; } } nextHandledException: for (int i = 0, count = this.handledExceptions.length; i < count; i++) { int index = this.indexes.get(this.handledExceptions[i]); if ((this.isReached[index / ExceptionHandlingFlowContext.BitCacheSize] & 1 << (index % ExceptionHandlingFlowContext.BitCacheSize)) == 0) { for (int j = 0; j < docCommentReferencesLength; j++) { if (TypeBinding.equalsEquals(docCommentReferences[j], this.handledExceptions[i])) { continue nextHandledException; } } scope.problemReporter().unusedDeclaredThrownException( this.handledExceptions[index], method, method.thrownExceptions[index]); } } } public void complainIfUnusedExceptionHandlers(BlockScope scope,TryStatement tryStatement) { // report errors for unreachable exception handlers for (int index = 0, count = this.handledExceptions.length; index < count; index++) { int cacheIndex = index / ExceptionHandlingFlowContext.BitCacheSize; int bitMask = 1 << (index % ExceptionHandlingFlowContext.BitCacheSize); if ((this.isReached[cacheIndex] & bitMask) == 0) { scope.problemReporter().unreachableCatchBlock( this.handledExceptions[index], getExceptionType(index)); } else { if ((this.isNeeded[cacheIndex] & bitMask) == 0) { scope.problemReporter().hiddenCatchBlock( this.handledExceptions[index], getExceptionType(index)); } } } } private ASTNode getExceptionType(int index) { if (this.exceptionToCatchBlockMap == null) { return this.catchArguments[index].type; } int catchBlock = this.exceptionToCatchBlockMap[index]; ASTNode node = this.catchArguments[catchBlock].type; if (node instanceof UnionTypeReference) { TypeReference[] typeRefs = ((UnionTypeReference)node).typeReferences; for (int i = 0, len = typeRefs.length; i < len; i++) { TypeReference typeRef = typeRefs[i]; if (TypeBinding.equalsEquals(typeRef.resolvedType, this.handledExceptions[index])) return typeRef; } } return node; } @Override public FlowContext getInitializationContext() { return this.initializationParent; } @Override public String individualToString() { StringBuffer buffer = new StringBuffer("Exception flow context"); //$NON-NLS-1$ int length = this.handledExceptions.length; for (int i = 0; i < length; i++) { int cacheIndex = i / ExceptionHandlingFlowContext.BitCacheSize; int bitMask = 1 << (i % ExceptionHandlingFlowContext.BitCacheSize); buffer.append('[').append(this.handledExceptions[i].readableName()); if ((this.isReached[cacheIndex] & bitMask) != 0) { if ((this.isNeeded[cacheIndex] & bitMask) == 0) { buffer.append("-masked"); //$NON-NLS-1$ } else { buffer.append("-reached"); //$NON-NLS-1$ } } else { buffer.append("-not reached"); //$NON-NLS-1$ } int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[i] : i; buffer.append('-').append(this.initsOnExceptions[catchBlock].toString()).append(']'); } buffer.append("[initsOnReturn -").append(this.initsOnReturn.toString()).append(']'); //$NON-NLS-1$ return buffer.toString(); } // WARNING: index is the catch block index as in the program order, before any normalization is // applied for multi catch public UnconditionalFlowInfo initsOnException(int index) { return this.initsOnExceptions[index]; } @Override public UnconditionalFlowInfo initsOnReturn(){ return this.initsOnReturn; } /* * Compute a merged list of unhandled exception types (keeping only the most generic ones). * This is necessary to add synthetic thrown exceptions for anonymous type constructors (JLS 8.6). */ public void mergeUnhandledException(TypeBinding newException){ if (this.extendedExceptions == null){ this.extendedExceptions = new ArrayList(5); for (int i = 0; i < this.handledExceptions.length; i++){ this.extendedExceptions.add(this.handledExceptions[i]); } } boolean isRedundant = false; for(int i = this.extendedExceptions.size()-1; i >= 0; i--){ switch(Scope.compareTypes(newException, (TypeBinding)this.extendedExceptions.get(i))){ case Scope.MORE_GENERIC : this.extendedExceptions.remove(i); break; case Scope.EQUAL_OR_MORE_SPECIFIC : isRedundant = true; break; case Scope.NOT_RELATED : break; } } if (!isRedundant){ this.extendedExceptions.add(newException); } } public void recordHandlingException( ReferenceBinding exceptionType, UnconditionalFlowInfo flowInfo, TypeBinding raisedException, TypeBinding caughtException, ASTNode invocationSite, boolean wasAlreadyDefinitelyCaught) { int index = this.indexes.get(exceptionType); int cacheIndex = index / ExceptionHandlingFlowContext.BitCacheSize; int bitMask = 1 << (index % ExceptionHandlingFlowContext.BitCacheSize); if (!wasAlreadyDefinitelyCaught) { this.isNeeded[cacheIndex] |= bitMask; } this.isReached[cacheIndex] |= bitMask; int catchBlock = this.exceptionToCatchBlockMap != null? this.exceptionToCatchBlockMap[index] : index; if (caughtException != null && this.catchArguments != null && this.catchArguments.length > 0 && !wasAlreadyDefinitelyCaught) { CatchParameterBinding catchParameter = (CatchParameterBinding) this.catchArguments[catchBlock].binding; catchParameter.setPreciseType(caughtException); } this.initsOnExceptions[catchBlock] = (this.initsOnExceptions[catchBlock].tagBits & FlowInfo.UNREACHABLE) == 0 ? this.initsOnExceptions[catchBlock].mergedWith(flowInfo): flowInfo.unconditionalCopy(); } @Override public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) { if ((this.initsOnReturn.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) { this.initsOnReturn = this.initsOnReturn.mergedWith(flowInfo); } else { this.initsOnReturn = (UnconditionalFlowInfo) flowInfo.copy(); } } }
Exception handlers (with no finally block) are also included with subroutine only once (in case parented with true InsideSubRoutineFlowContext). Standard management of subroutines need to also operate on intermediate exception handlers.
See Also:
  • subroutine.subroutine()
/** * Exception handlers (with no finally block) are also included with subroutine * only once (in case parented with true InsideSubRoutineFlowContext). * Standard management of subroutines need to also operate on intermediate * exception handlers. * @see org.eclipse.jdt.internal.compiler.flow.FlowContext#subroutine() */
@Override public SubRoutineStatement subroutine() { if (this.associatedNode instanceof SubRoutineStatement) { // exception handler context may be child of InsideSubRoutineFlowContext, which maps to same handler if (this.parent.subroutine() == this.associatedNode) return null; return (SubRoutineStatement) this.associatedNode; } return null; } }