/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-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.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.ASTNode; 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.CompilerModifiers; 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; /** * Reflects the context of code analysis, keeping track of enclosing * try statements, exception handlers, etc... */ public class ExceptionHandlingFlowContext extends FlowContext { public ReferenceBinding[] handledExceptions; public final static int BitCacheSize = 32; // 32 bits per int int[] isReached; int[] isNeeded; UnconditionalFlowInfo[] initsOnExceptions; ObjectCache indexes = new ObjectCache(); boolean isMethodContext; public UnconditionalFlowInfo initsOnReturn; // for dealing with anonymous constructor thrown exceptions public ArrayList extendedExceptions; public ExceptionHandlingFlowContext( FlowContext parent, ASTNode associatedNode, ReferenceBinding[] handledExceptions, BlockScope scope, UnconditionalFlowInfo flowInfo) { super(parent, associatedNode); isMethodContext = scope == scope.methodScope(); this.handledExceptions = handledExceptions; int count = handledExceptions.length, cacheSize = (count / 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]; for (int i = 0; i < count; i++) { this.indexes.put(handledExceptions[i], i); // key type -> value index boolean isUnchecked = (scope.compareUncheckedException(handledExceptions[i]) != NotRelated); int cacheIndex = i / BitCacheSize, bitMask = 1 << (i % BitCacheSize); if (isUnchecked) { isReached[cacheIndex] |= bitMask; this.initsOnExceptions[i] = flowInfo.copy().unconditionalInits(); } else { this.initsOnExceptions[i] = FlowInfo.DEAD_END; } } System.arraycopy(this.isReached, 0, this.isNeeded, 0, cacheSize); this.initsOnReturn = FlowInfo.DEAD_END; } public void complainIfUnusedExceptionHandlers(AbstractMethodDeclaration method) { MethodScope scope = method.scope; // can optionally skip overriding methods if ((method.binding.modifiers & (CompilerModifiers.AccOverriding | CompilerModifiers.AccImplementing)) != 0 && !scope.environment().options.reportUnusedDeclaredThrownExceptionWhenOverriding) { return; } // report errors for unreachable exception handlers for (int i = 0, count = handledExceptions.length; i < count; i++) { int index = indexes.get(handledExceptions[i]); int cacheIndex = index / BitCacheSize; int bitMask = 1 << (index % BitCacheSize); if ((isReached[cacheIndex] & bitMask) == 0) { scope.problemReporter().unusedDeclaredThrownException( handledExceptions[index], method, method.thrownExceptions[index]); } } } public void complainIfUnusedExceptionHandlers( BlockScope scope, TryStatement tryStatement) { // report errors for unreachable exception handlers for (int i = 0, count = handledExceptions.length; i < count; i++) { int index = indexes.get(handledExceptions[i]); int cacheIndex = index / BitCacheSize; int bitMask = 1 << (index % BitCacheSize); if ((isReached[cacheIndex] & bitMask) == 0) { scope.problemReporter().unreachableCatchBlock( handledExceptions[index], tryStatement.catchArguments[index].type); } else { if ((isNeeded[cacheIndex] & bitMask) == 0) { scope.problemReporter().hiddenCatchBlock( handledExceptions[index], tryStatement.catchArguments[index].type); } } } } public String individualToString() { StringBuffer buffer = new StringBuffer("Exception flow context"); //$NON-NLS-1$ int length = handledExceptions.length; for (int i = 0; i < length; i++) { int cacheIndex = i / BitCacheSize; int bitMask = 1 << (i % BitCacheSize); buffer.append('[').append(handledExceptions[i].readableName()); if ((isReached[cacheIndex] & bitMask) != 0) { if ((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(initsOnExceptions[i].toString()).append(']'); } buffer.append("[initsOnReturn -").append(initsOnReturn.toString()).append(']'); //$NON-NLS-1$ return buffer.toString(); } public UnconditionalFlowInfo initsOnException(ReferenceBinding exceptionType) { int index; if ((index = indexes.get(exceptionType)) < 0) { return FlowInfo.DEAD_END; } return initsOnExceptions[index]; } public UnconditionalFlowInfo initsOnReturn(){ return this.initsOnReturn; } public void recordHandlingException( ReferenceBinding exceptionType, UnconditionalFlowInfo flowInfo, TypeBinding raisedException, ASTNode invocationSite, boolean wasAlreadyDefinitelyCaught) { int index = indexes.get(exceptionType); // if already flagged as being reached (unchecked exception handler) int cacheIndex = index / BitCacheSize; int bitMask = 1 << (index % BitCacheSize); if (!wasAlreadyDefinitelyCaught) { this.isNeeded[cacheIndex] |= bitMask; } this.isReached[cacheIndex] |= bitMask; initsOnExceptions[index] = initsOnExceptions[index] == FlowInfo.DEAD_END ? flowInfo.copy().unconditionalInits() : initsOnExceptions[index].mergedWith(flowInfo.copy().unconditionalInits()); } public void recordReturnFrom(FlowInfo flowInfo) { if (!flowInfo.isReachable()) return; if (initsOnReturn == FlowInfo.DEAD_END) { initsOnReturn = flowInfo.copy().unconditionalInits(); } else { initsOnReturn = initsOnReturn.mergedWith(flowInfo.copy().unconditionalInits()); } } /* * 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 MoreGeneric : this.extendedExceptions.remove(i); break; case EqualOrMoreSpecific : isRedundant = true; break; case NotRelated : break; } } if (!isRedundant){ this.extendedExceptions.add(newException); } } }