package org.eclipse.jdt.internal.compiler.flow; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.*; | |
import org.eclipse.jdt.internal.compiler.*; | |
import org.eclipse.jdt.internal.compiler.ast.*; | |
import org.eclipse.jdt.internal.compiler.codegen.*; | |
import org.eclipse.jdt.internal.compiler.lookup.*; | |
import org.eclipse.jdt.internal.compiler.problem.*; | |
import org.eclipse.jdt.internal.compiler.util.*; | |
/** | |
* 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) { | |
AstNode sub; | |
if (((sub = traversedContext.subRoutine()) != null) && sub.cannotReturn()) { | |
// traversing a non-returning subroutine means that all unhandled | |
// exceptions will actually never get sent... | |
return; | |
} | |
// filter exceptions that are locally caught from the most enclosing | |
// try statement to the outer 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) { | |
switch (scope.compareTypes(raisedException, caughtException)) { | |
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 (scope.areTypesCompatible(raisedException, scope.getJavaLangRuntimeException()) | |
|| scope.areTypesCompatible(raisedException, scope.getJavaLangError())){ | |
remainingCount --; | |
raisedExceptions[i] = null; | |
} | |
} | |
} | |
} | |
} | |
if (remainingCount == 0) | |
return; | |
traversedContext = traversedContext.parent; | |
} | |
// if reaches this point, then there are some remaining unhandled exception types. | |
for (int i = 0; i < raisedCount; i++) { | |
TypeBinding exception; | |
if ((exception = raisedExceptions[i]) != null) { | |
scope.problemReporter().unhandledException(exception, location, scope); | |
} | |
} | |
} | |
/* | |
" - SMARTER VERSION - | |
| unhandledExceptionTypes nameEnv traversedContext | | |
someExceptionTypes isEmpty ifTrue: [^self]. | |
unhandledExceptionTypes := someExceptionTypes asOrderedCollection. | |
nameEnv := scope enclosingMethod nameEnvironment. | |
traversedContext := self. | |
[traversedContext isNil] whileFalse: [| caughtExceptions sub | | |
((sub := traversedContext subRoutine) notNil and: [sub cannotReturn]) | |
ifTrue: [ | |
" "Traversing a non-returning subroutine means that all unhandled exceptions will actually | |
never get sent..." " | |
^self]. | |
" "Filter exceptions that are locally caught from the most enclosing try statement to the outer ones." " | |
(caughtExceptions := traversedContext handledExceptions) isNil | |
ifFalse: [ | |
caughtExceptions do: [:handledExceptionAssoc | | handledException | | |
handledException := handledExceptionAssoc key. | |
unhandledExceptionTypes copy do: [:raisedException | | safe | | |
" "Any exception recognized as being caught is removed from the exceptions list" " | |
((safe := raisedException isCompatibleWith: handledException in: nameEnv) | |
or: [handledException isCompatibleWith: raisedException in: nameEnv]) | |
ifTrue: [ | |
traversedContext | |
recordInitializationInfo: initInfo | |
onException: handledException. | |
handledExceptionAssoc value: true. | |
safe ifTrue: [unhandledExceptionTypes remove: raisedException]]]]]. | |
unhandledExceptionTypes isEmpty ifTrue: [^self]. | |
traversedContext := traversedContext parent]. | |
scope enclosingMethod errorInterface | |
unexpectedExceptionsError: unhandledExceptionTypes | |
from: invocationSite | |
*/ | |
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) { | |
AstNode sub; | |
if (((sub = traversedContext.subRoutine()) != null) && sub.cannotReturn()) { | |
// traversing a non-returning subroutine means that all unhandled | |
// exceptions will actually never get sent... | |
return; | |
} | |
// filter exceptions that are locally caught from the most enclosing | |
// try statement to the outer 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]; | |
switch (scope.compareTypes(raisedException, caughtException)) { | |
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 (scope.areTypesCompatible(raisedException, scope.getJavaLangRuntimeException()) | |
|| scope.areTypesCompatible(raisedException, scope.getJavaLangError())) | |
return; | |
break; // not handled anywhere, thus jump to error handling | |
} | |
} | |
traversedContext = traversedContext.parent; | |
} | |
// if reaches this point, then there are some remaining unhandled exception types. | |
scope.problemReporter().unhandledException(raisedException, location, scope); | |
} | |
public Label continueLabel() { | |
return null; | |
} | |
public FlowContext getTargetContextForBreakLabel(char[] labelName) { | |
// lookup through break labels | |
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; | |
} else { | |
return lastNonReturningSubRoutine; | |
} | |
} | |
current = current.parent; | |
} | |
// not found | |
return null; | |
} | |
public FlowContext getTargetContextForContinueLabel(char[] labelName) { | |
// lookup through continue labels | |
FlowContext current = this, lastContinuable = null, 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)) { | |
if ((lastContinuable != null) && (current.associatedNode.concreteStatement() == lastContinuable.associatedNode)) { | |
if (lastNonReturningSubRoutine == null) { | |
return lastContinuable; | |
} else { | |
return lastNonReturningSubRoutine; | |
} | |
} else { | |
// label is found, but not a continuable location | |
return NotContinuableContext; | |
} | |
} | |
current = current.parent; | |
} | |
// not found | |
return null; | |
} | |
public FlowContext getTargetContextForDefaultBreak() { | |
// lookup through break labels | |
FlowContext current = this, lastNonReturningSubRoutine = null; | |
while (current != null) { | |
if (current.isNonReturningContext()) { | |
lastNonReturningSubRoutine = current; | |
} | |
if (current.isBreakable()) { | |
if (lastNonReturningSubRoutine == null) { | |
return current; | |
} else { | |
return lastNonReturningSubRoutine; | |
} | |
} | |
current = current.parent; | |
} | |
// not found | |
return null; | |
} | |
public FlowContext getTargetContextForDefaultContinue() { | |
// lookup through continue labels | |
FlowContext current = this, lastNonReturningSubRoutine = null; | |
while (current != null) { | |
if (current.isNonReturningContext()) { | |
lastNonReturningSubRoutine = current; | |
} | |
if (current.isContinuable()) { | |
if (lastNonReturningSubRoutine == null) { | |
return current; | |
} else { | |
return lastNonReturningSubRoutine; | |
} | |
} | |
current = current.parent; | |
} | |
// not found | |
return null; | |
} | |
public String individualToString(){ | |
return "Flow context"/*nonNLS*/; | |
} | |
public FlowInfo initsOnBreak() { | |
return FlowInfo.DeadEnd; | |
} | |
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) { | |
} | |
public void recordContinueFrom(FlowInfo flowInfo) { | |
} | |
boolean recordFinalAssignment(VariableBinding variable, Reference finalReference) { | |
return true; // keep going | |
} | |
public void recordReturnFrom(UnconditionalFlowInfo flowInfo) { | |
} | |
public void recordSettingFinal(VariableBinding variable, Reference finalReference) { | |
// 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) { | |
} | |
public AstNode 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(); | |
} | |
} |