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