/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.flow; import java.util.ArrayList; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.codegen.ObjectCache; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; 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... */ public class ExceptionHandlingFlowContext extends FlowContext { public final static int BitCacheSize= 32; // 32 bits per int public ReferenceBinding[] handledExceptions; int[] isReached; int[] isNeeded; 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; public ExceptionHandlingFlowContext( FlowContext parent, ASTNode associatedNode, ReferenceBinding[] handledExceptions, FlowContext initializationParent, BlockScope scope, UnconditionalFlowInfo flowInfo) { super(parent, associatedNode); this.isMethodContext= scope == scope.methodScope(); this.handledExceptions= handledExceptions; 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]; 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[i]= flowInfo.unconditionalCopy(); } else { this.initsOnExceptions[i]= 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 (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 i= 0, count= this.handledExceptions.length; i < count; i++) { int index= this.indexes.get(this.handledExceptions[i]); int cacheIndex= index / ExceptionHandlingFlowContext.BitCacheSize; int bitMask= 1 << (index % ExceptionHandlingFlowContext.BitCacheSize); if ((this.isReached[cacheIndex] & bitMask) == 0) { scope.problemReporter().unreachableCatchBlock( this.handledExceptions[index], tryStatement.catchArguments[index].type); } else { if ((this.isNeeded[cacheIndex] & bitMask) == 0) { scope.problemReporter().hiddenCatchBlock( this.handledExceptions[index], tryStatement.catchArguments[index].type); } } } } 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$ } buffer.append('-').append(this.initsOnExceptions[i].toString()).append(']'); } buffer.append("[initsOnReturn -").append(this.initsOnReturn.toString()).append(']'); //$NON-NLS-1$ return buffer.toString(); } public UnconditionalFlowInfo initsOnException(ReferenceBinding exceptionType) { int index; if ((index= this.indexes.get(exceptionType)) < 0) { return FlowInfo.DEAD_END; } return this.initsOnExceptions[index]; } 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, 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; this.initsOnExceptions[index]= (this.initsOnExceptions[index].tagBits & FlowInfo.UNREACHABLE) == 0 ? this.initsOnExceptions[index].mergedWith(flowInfo) : flowInfo.unconditionalCopy(); } public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) { if ((this.initsOnReturn.tagBits & FlowInfo.UNREACHABLE) == 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 org.eclipse.jdt.internal.compiler.flow.FlowContext#subroutine() */ 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; } }