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