blob: ce08eaea4fb40067f0cbc7fc948e9cb62fd80b13 [file] [log] [blame]
/*******************************************************************************
* 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 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 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.Expression;
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.LocalVariableBinding;
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){
TryStatement tryStatement = (TryStatement) traversedContext.associatedNode;
flowInfo = flowInfo.copy().addInitializationsFrom(tryStatement.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){
TryStatement tryStatement = (TryStatement) traversedContext.associatedNode;
flowInfo = flowInfo.copy().addInitializationsFrom(tryStatement.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
}
protected boolean recordFinalAssignment(
VariableBinding variable,
Reference finalReference) {
return true; // keep going
}
protected boolean recordNullReference(Expression expression, int status) {
return false; // 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;
}
}
public void recordUsingNullReference(Scope scope, LocalVariableBinding local, Expression reference, int status, FlowInfo flowInfo) {
if (!flowInfo.isReachable()) return;
switch (status) {
case FlowInfo.NULL :
if (flowInfo.isDefinitelyNull(local)) {
scope.problemReporter().localVariableCanOnlyBeNull(local, reference);
return;
} else if (flowInfo.isDefinitelyNonNull(local)) {
scope.problemReporter().localVariableCannotBeNull(local, reference);
return;
}
break;
case FlowInfo.NON_NULL :
if (flowInfo.isDefinitelyNull(local)) {
scope.problemReporter().localVariableCanOnlyBeNull(local, reference);
return;
}
break;
}
// for initialization inside looping statement that effectively loops
FlowContext context = this;
while (context != null) {
if (context.recordNullReference(reference, status)) {
return; // 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();
}
}