| /******************************************************************************* |
| * 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 org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.Reference; |
| import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement; |
| import org.eclipse.jdt.internal.compiler.ast.TryStatement; |
| import org.eclipse.jdt.internal.compiler.codegen.Label; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| 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.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; |
| |
| /** |
| * Reflects the context of code analysis, keeping track of enclosing |
| * try statements, exception handlers, etc... |
| */ |
| public class FlowContext implements TypeConstants { |
| |
| public ASTNode associatedNode; |
| public FlowContext parent; |
| |
| public final static FlowContext NotContinuableContext = new FlowContext(null, null); |
| |
| public FlowContext(FlowContext parent, ASTNode associatedNode) { |
| |
| this.parent = parent; |
| this.associatedNode = associatedNode; |
| } |
| |
| public Label breakLabel() { |
| |
| return null; |
| } |
| |
| public void checkExceptionHandlers( |
| TypeBinding[] raisedExceptions, |
| ASTNode location, |
| FlowInfo flowInfo, |
| BlockScope scope) { |
| |
| // check that all the argument exception types are handled |
| // JDK Compatible implementation - when an exception type is thrown, |
| // all related catch blocks are marked as reachable... instead of those only |
| // until the point where it is safely handled (Smarter - see comment at the end) |
| int remainingCount; // counting the number of remaining unhandled exceptions |
| int raisedCount; // total number of exceptions raised |
| if ((raisedExceptions == null) |
| || ((raisedCount = raisedExceptions.length) == 0)) |
| return; |
| remainingCount = raisedCount; |
| |
| // duplicate the array of raised exceptions since it will be updated |
| // (null replaces any handled exception) |
| System.arraycopy( |
| raisedExceptions, |
| 0, |
| (raisedExceptions = new TypeBinding[raisedCount]), |
| 0, |
| raisedCount); |
| FlowContext traversedContext = this; |
| |
| while (traversedContext != null) { |
| SubRoutineStatement sub; |
| if (((sub = traversedContext.subRoutine()) != null) && sub.isSubRoutineEscaping()) { |
| // traversing a non-returning subroutine means that all unhandled |
| // exceptions will actually never get sent... |
| return; |
| } |
| // filter exceptions that are locally caught from the innermost enclosing |
| // try statement to the outermost ones. |
| if (traversedContext instanceof ExceptionHandlingFlowContext) { |
| ExceptionHandlingFlowContext exceptionContext = |
| (ExceptionHandlingFlowContext) traversedContext; |
| ReferenceBinding[] caughtExceptions; |
| if ((caughtExceptions = exceptionContext.handledExceptions) != NoExceptions) { |
| int caughtCount = caughtExceptions.length; |
| boolean[] locallyCaught = new boolean[raisedCount]; // at most |
| |
| for (int caughtIndex = 0; caughtIndex < caughtCount; caughtIndex++) { |
| ReferenceBinding caughtException = caughtExceptions[caughtIndex]; |
| for (int raisedIndex = 0; raisedIndex < raisedCount; raisedIndex++) { |
| TypeBinding raisedException; |
| if ((raisedException = raisedExceptions[raisedIndex]) != null) { |
| int state = caughtException == null |
| ? EqualOrMoreSpecific /* any exception */ |
| : Scope.compareTypes(raisedException, caughtException); |
| switch (state) { |
| case EqualOrMoreSpecific : |
| exceptionContext.recordHandlingException( |
| caughtException, |
| flowInfo.unconditionalInits(), |
| raisedException, |
| location, |
| locallyCaught[raisedIndex]); |
| // was already definitely caught ? |
| if (!locallyCaught[raisedIndex]) { |
| locallyCaught[raisedIndex] = true; |
| // remember that this exception has been definitely caught |
| remainingCount--; |
| } |
| break; |
| case MoreGeneric : |
| exceptionContext.recordHandlingException( |
| caughtException, |
| flowInfo.unconditionalInits(), |
| raisedException, |
| location, |
| false); |
| // was not caught already per construction |
| } |
| } |
| } |
| } |
| // remove locally caught exceptions from the remaining ones |
| for (int i = 0; i < raisedCount; i++) { |
| if (locallyCaught[i]) { |
| raisedExceptions[i] = null; // removed from the remaining ones. |
| } |
| } |
| } |
| // method treatment for unchecked exceptions |
| if (exceptionContext.isMethodContext) { |
| for (int i = 0; i < raisedCount; i++) { |
| TypeBinding raisedException; |
| if ((raisedException = raisedExceptions[i]) != null) { |
| if (raisedException.isCompatibleWith(scope.getJavaLangRuntimeException()) |
| || raisedException.isCompatibleWith(scope.getJavaLangError())) { |
| remainingCount--; |
| raisedExceptions[i] = null; |
| } |
| } |
| } |
| // anonymous constructors are allowed to throw any exceptions (their thrown exceptions |
| // clause will be fixed up later as per JLS 8.6). |
| if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration){ |
| AbstractMethodDeclaration method = (AbstractMethodDeclaration)exceptionContext.associatedNode; |
| if (method.isConstructor() && method.binding.declaringClass.isAnonymousType()){ |
| |
| for (int i = 0; i < raisedCount; i++) { |
| TypeBinding raisedException; |
| if ((raisedException = raisedExceptions[i]) != null) { |
| exceptionContext.mergeUnhandledException(raisedException); |
| } |
| } |
| return; // no need to complain, will fix up constructor exceptions |
| } |
| } |
| break; // not handled anywhere, thus jump to error handling |
| } |
| } |
| if (remainingCount == 0) |
| return; |
| |
| traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); |
| if (traversedContext.associatedNode instanceof TryStatement){ |
| flowInfo = flowInfo.copy().addInitializationsFrom(((TryStatement) traversedContext.associatedNode).subRoutineInits); |
| } |
| traversedContext = traversedContext.parent; |
| } |
| // if reaches this point, then there are some remaining unhandled exception types. |
| nextReport: for (int i = 0; i < raisedCount; i++) { |
| TypeBinding exception; |
| if ((exception = raisedExceptions[i]) != null) { |
| // only one complaint if same exception declared to be thrown more than once |
| for (int j = 0; j < i; j++) { |
| if (raisedExceptions[j] == exception) continue nextReport; // already reported |
| } |
| scope.problemReporter().unhandledException(exception, location); |
| } |
| } |
| } |
| |
| public void checkExceptionHandlers( |
| TypeBinding raisedException, |
| ASTNode location, |
| FlowInfo flowInfo, |
| BlockScope scope) { |
| |
| // LIGHT-VERSION OF THE EQUIVALENT WITH AN ARRAY OF EXCEPTIONS |
| // check that all the argument exception types are handled |
| // JDK Compatible implementation - when an exception type is thrown, |
| // all related catch blocks are marked as reachable... instead of those only |
| // until the point where it is safely handled (Smarter - see comment at the end) |
| FlowContext traversedContext = this; |
| while (traversedContext != null) { |
| SubRoutineStatement sub; |
| if (((sub = traversedContext.subRoutine()) != null) && sub.isSubRoutineEscaping()) { |
| // traversing a non-returning subroutine means that all unhandled |
| // exceptions will actually never get sent... |
| return; |
| } |
| |
| // filter exceptions that are locally caught from the innermost enclosing |
| // try statement to the outermost ones. |
| if (traversedContext instanceof ExceptionHandlingFlowContext) { |
| ExceptionHandlingFlowContext exceptionContext = |
| (ExceptionHandlingFlowContext) traversedContext; |
| ReferenceBinding[] caughtExceptions; |
| if ((caughtExceptions = exceptionContext.handledExceptions) != NoExceptions) { |
| boolean definitelyCaught = false; |
| for (int caughtIndex = 0, caughtCount = caughtExceptions.length; |
| caughtIndex < caughtCount; |
| caughtIndex++) { |
| ReferenceBinding caughtException = caughtExceptions[caughtIndex]; |
| int state = caughtException == null |
| ? EqualOrMoreSpecific /* any exception */ |
| : Scope.compareTypes(raisedException, caughtException); |
| switch (state) { |
| case EqualOrMoreSpecific : |
| exceptionContext.recordHandlingException( |
| caughtException, |
| flowInfo.unconditionalInits(), |
| raisedException, |
| location, |
| definitelyCaught); |
| // was it already definitely caught ? |
| definitelyCaught = true; |
| break; |
| case MoreGeneric : |
| exceptionContext.recordHandlingException( |
| caughtException, |
| flowInfo.unconditionalInits(), |
| raisedException, |
| location, |
| false); |
| // was not caught already per construction |
| } |
| } |
| if (definitelyCaught) |
| return; |
| } |
| // method treatment for unchecked exceptions |
| if (exceptionContext.isMethodContext) { |
| if (raisedException.isCompatibleWith(scope.getJavaLangRuntimeException()) |
| || raisedException.isCompatibleWith(scope.getJavaLangError())) |
| return; |
| |
| // anonymous constructors are allowed to throw any exceptions (their thrown exceptions |
| // clause will be fixed up later as per JLS 8.6). |
| if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration){ |
| AbstractMethodDeclaration method = (AbstractMethodDeclaration)exceptionContext.associatedNode; |
| if (method.isConstructor() && method.binding.declaringClass.isAnonymousType()){ |
| |
| exceptionContext.mergeUnhandledException(raisedException); |
| return; // no need to complain, will fix up constructor exceptions |
| } |
| } |
| break; // not handled anywhere, thus jump to error handling |
| } |
| } |
| |
| traversedContext.recordReturnFrom(flowInfo.unconditionalInits()); |
| if (traversedContext.associatedNode instanceof TryStatement){ |
| flowInfo = flowInfo.copy().addInitializationsFrom(((TryStatement) traversedContext.associatedNode).subRoutineInits); |
| } |
| traversedContext = traversedContext.parent; |
| } |
| // if reaches this point, then there are some remaining unhandled exception types. |
| scope.problemReporter().unhandledException(raisedException, location); |
| } |
| |
| public Label continueLabel() { |
| |
| return null; |
| } |
| |
| /* |
| * lookup through break labels |
| */ |
| public FlowContext getTargetContextForBreakLabel(char[] labelName) { |
| |
| FlowContext current = this, lastNonReturningSubRoutine = null; |
| while (current != null) { |
| if (current.isNonReturningContext()) { |
| lastNonReturningSubRoutine = current; |
| } |
| char[] currentLabelName; |
| if (((currentLabelName = current.labelName()) != null) |
| && CharOperation.equals(currentLabelName, labelName)) { |
| if (lastNonReturningSubRoutine == null) |
| return current; |
| return lastNonReturningSubRoutine; |
| } |
| current = current.parent; |
| } |
| // not found |
| return null; |
| } |
| |
| /* |
| * lookup through continue labels |
| */ |
| public FlowContext getTargetContextForContinueLabel(char[] labelName) { |
| |
| FlowContext current = this; |
| FlowContext lastContinuable = null; |
| FlowContext lastNonReturningSubRoutine = null; |
| |
| while (current != null) { |
| if (current.isNonReturningContext()) { |
| lastNonReturningSubRoutine = current; |
| } else { |
| if (current.isContinuable()) { |
| lastContinuable = current; |
| } |
| } |
| |
| char[] currentLabelName; |
| if ((currentLabelName = current.labelName()) != null && CharOperation.equals(currentLabelName, labelName)) { |
| |
| // matching label found |
| if ((lastContinuable != null) |
| && (current.associatedNode.concreteStatement() == lastContinuable.associatedNode)) { |
| |
| if (lastNonReturningSubRoutine == null) return lastContinuable; |
| return lastNonReturningSubRoutine; |
| } |
| // label is found, but not a continuable location |
| return NotContinuableContext; |
| } |
| current = current.parent; |
| } |
| // not found |
| return null; |
| } |
| |
| /* |
| * lookup a default break through breakable locations |
| */ |
| public FlowContext getTargetContextForDefaultBreak() { |
| |
| FlowContext current = this, lastNonReturningSubRoutine = null; |
| while (current != null) { |
| if (current.isNonReturningContext()) { |
| lastNonReturningSubRoutine = current; |
| } |
| if (current.isBreakable() && current.labelName() == null) { |
| if (lastNonReturningSubRoutine == null) return current; |
| return lastNonReturningSubRoutine; |
| } |
| current = current.parent; |
| } |
| // not found |
| return null; |
| } |
| |
| /* |
| * lookup a default continue amongst continuable locations |
| */ |
| public FlowContext getTargetContextForDefaultContinue() { |
| |
| FlowContext current = this, lastNonReturningSubRoutine = null; |
| while (current != null) { |
| if (current.isNonReturningContext()) { |
| lastNonReturningSubRoutine = current; |
| } |
| if (current.isContinuable()) { |
| if (lastNonReturningSubRoutine == null) |
| return current; |
| return lastNonReturningSubRoutine; |
| } |
| current = current.parent; |
| } |
| // not found |
| return null; |
| } |
| |
| public String individualToString() { |
| |
| return "Flow context"; //$NON-NLS-1$ |
| } |
| |
| public FlowInfo initsOnBreak() { |
| |
| return FlowInfo.DEAD_END; |
| } |
| |
| public UnconditionalFlowInfo initsOnReturn() { |
| |
| return FlowInfo.DEAD_END; |
| } |
| |
| public boolean isBreakable() { |
| |
| return false; |
| } |
| |
| public boolean isContinuable() { |
| |
| return false; |
| } |
| |
| public boolean isNonReturningContext() { |
| |
| return false; |
| } |
| |
| public boolean isSubRoutine() { |
| |
| return false; |
| } |
| |
| public char[] labelName() { |
| |
| return null; |
| } |
| |
| public void recordBreakFrom(FlowInfo flowInfo) { |
| // default implementation: do nothing |
| } |
| |
| public void recordContinueFrom(FlowInfo flowInfo) { |
| // default implementation: do nothing |
| } |
| |
| boolean recordFinalAssignment( |
| VariableBinding variable, |
| Reference finalReference) { |
| |
| return true; // keep going |
| } |
| |
| public void recordReturnFrom(FlowInfo flowInfo) { |
| // default implementation: do nothing |
| } |
| |
| public void recordSettingFinal( |
| VariableBinding variable, |
| Reference finalReference, |
| FlowInfo flowInfo) { |
| |
| if (!flowInfo.isReachable()) return; |
| |
| // for initialization inside looping statement that effectively loops |
| FlowContext context = this; |
| while (context != null) { |
| if (!context.recordFinalAssignment(variable, finalReference)) { |
| break; // no need to keep going |
| } |
| context = context.parent; |
| } |
| } |
| |
| void removeFinalAssignmentIfAny(Reference reference) { |
| // default implementation: do nothing |
| } |
| |
| public SubRoutineStatement subRoutine() { |
| |
| return null; |
| } |
| |
| public String toString() { |
| |
| StringBuffer buffer = new StringBuffer(); |
| FlowContext current = this; |
| int parentsCount = 0; |
| while ((current = current.parent) != null) { |
| parentsCount++; |
| } |
| FlowContext[] parents = new FlowContext[parentsCount + 1]; |
| current = this; |
| int index = parentsCount; |
| while (index >= 0) { |
| parents[index--] = current; |
| current = current.parent; |
| } |
| for (int i = 0; i < parentsCount; i++) { |
| for (int j = 0; j < i; j++) |
| buffer.append('\t'); |
| buffer.append(parents[i].individualToString()).append('\n'); |
| } |
| buffer.append('*'); |
| for (int j = 0; j < parentsCount + 1; j++) |
| buffer.append('\t'); |
| buffer.append(individualToString()).append('\n'); |
| return buffer.toString(); |
| } |
| } |