001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.compiler.flow;
011:
012: import org.eclipse.jdt.core.compiler.CharOperation;
013: import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
014: import org.eclipse.jdt.internal.compiler.ast.ASTNode;
015: import org.eclipse.jdt.internal.compiler.ast.Expression;
016: import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
017: import org.eclipse.jdt.internal.compiler.ast.Reference;
018: import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement;
019: import org.eclipse.jdt.internal.compiler.ast.TryStatement;
020: import org.eclipse.jdt.internal.compiler.codegen.BranchLabel;
021: import org.eclipse.jdt.internal.compiler.lookup.Binding;
022: import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
023: import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
024: import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
025: import org.eclipse.jdt.internal.compiler.lookup.Scope;
026: import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
027: import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
028: import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
029:
030: /**
031: * Reflects the context of code analysis, keeping track of enclosing
032: * try statements, exception handlers, etc...
033: */
034: public class FlowContext implements TypeConstants {
035:
036: // preempt marks looping contexts
037: public final static FlowContext NotContinuableContext = new FlowContext(
038: null, null);
039: public ASTNode associatedNode;
040: public FlowContext parent;
041: public NullInfoRegistry initsOnFinally;
042: // only used within try blocks; remembers upstream flow info mergedWith
043: // any null related operation happening within the try block
044:
045: boolean deferNullDiagnostic, preemptNullDiagnostic;
046:
047: public FlowContext(FlowContext parent, ASTNode associatedNode) {
048: this .parent = parent;
049: this .associatedNode = associatedNode;
050: if (parent != null) {
051: this .deferNullDiagnostic = parent.deferNullDiagnostic
052: || parent.preemptNullDiagnostic;
053: this .initsOnFinally = parent.initsOnFinally;
054: }
055: }
056:
057: public BranchLabel breakLabel() {
058: return null;
059: }
060:
061: public void checkExceptionHandlers(TypeBinding raisedException,
062: ASTNode location, FlowInfo flowInfo, BlockScope scope) {
063: // LIGHT-VERSION OF THE EQUIVALENT WITH AN ARRAY OF EXCEPTIONS
064: // check that all the argument exception types are handled
065: // JDK Compatible implementation - when an exception type is thrown,
066: // all related catch blocks are marked as reachable... instead of those only
067: // until the point where it is safely handled (Smarter - see comment at the end)
068: FlowContext traversedContext = this ;
069: while (traversedContext != null) {
070: SubRoutineStatement sub;
071: if (((sub = traversedContext.subroutine()) != null)
072: && sub.isSubRoutineEscaping()) {
073: // traversing a non-returning subroutine means that all unhandled
074: // exceptions will actually never get sent...
075: return;
076: }
077:
078: // filter exceptions that are locally caught from the innermost enclosing
079: // try statement to the outermost ones.
080: if (traversedContext instanceof ExceptionHandlingFlowContext) {
081: ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext;
082: ReferenceBinding[] caughtExceptions;
083: if ((caughtExceptions = exceptionContext.handledExceptions) != Binding.NO_EXCEPTIONS) {
084: boolean definitelyCaught = false;
085: for (int caughtIndex = 0, caughtCount = caughtExceptions.length; caughtIndex < caughtCount; caughtIndex++) {
086: ReferenceBinding caughtException = caughtExceptions[caughtIndex];
087: int state = caughtException == null ? Scope.EQUAL_OR_MORE_SPECIFIC /* any exception */
088: : Scope.compareTypes(raisedException,
089: caughtException);
090: switch (state) {
091: case Scope.EQUAL_OR_MORE_SPECIFIC:
092: exceptionContext.recordHandlingException(
093: caughtException, flowInfo
094: .unconditionalInits(),
095: raisedException, location,
096: definitelyCaught);
097: // was it already definitely caught ?
098: definitelyCaught = true;
099: break;
100: case Scope.MORE_GENERIC:
101: exceptionContext.recordHandlingException(
102: caughtException, flowInfo
103: .unconditionalInits(),
104: raisedException, location, false);
105: // was not caught already per construction
106: }
107: }
108: if (definitelyCaught)
109: return;
110: }
111: // method treatment for unchecked exceptions
112: if (exceptionContext.isMethodContext) {
113: if (raisedException.isUncheckedException(false))
114: return;
115:
116: // anonymous constructors are allowed to throw any exceptions (their thrown exceptions
117: // clause will be fixed up later as per JLS 8.6).
118: if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) {
119: AbstractMethodDeclaration method = (AbstractMethodDeclaration) exceptionContext.associatedNode;
120: if (method.isConstructor()
121: && method.binding.declaringClass
122: .isAnonymousType()) {
123:
124: exceptionContext
125: .mergeUnhandledException(raisedException);
126: return; // no need to complain, will fix up constructor exceptions
127: }
128: }
129: break; // not handled anywhere, thus jump to error handling
130: }
131: }
132:
133: traversedContext.recordReturnFrom(flowInfo
134: .unconditionalInits());
135:
136: if (traversedContext instanceof InsideSubRoutineFlowContext) {
137: ASTNode node = traversedContext.associatedNode;
138: if (node instanceof TryStatement) {
139: TryStatement tryStatement = (TryStatement) node;
140: flowInfo
141: .addInitializationsFrom(tryStatement.subRoutineInits); // collect inits
142: }
143: }
144: traversedContext = traversedContext.parent;
145: }
146: // if reaches this point, then there are some remaining unhandled exception types.
147: scope.problemReporter().unhandledException(raisedException,
148: location);
149: }
150:
151: public void checkExceptionHandlers(TypeBinding[] raisedExceptions,
152: ASTNode location, FlowInfo flowInfo, BlockScope scope) {
153: // check that all the argument exception types are handled
154: // JDK Compatible implementation - when an exception type is thrown,
155: // all related catch blocks are marked as reachable... instead of those only
156: // until the point where it is safely handled (Smarter - see comment at the end)
157: int remainingCount; // counting the number of remaining unhandled exceptions
158: int raisedCount; // total number of exceptions raised
159: if ((raisedExceptions == null)
160: || ((raisedCount = raisedExceptions.length) == 0))
161: return;
162: remainingCount = raisedCount;
163:
164: // duplicate the array of raised exceptions since it will be updated
165: // (null replaces any handled exception)
166: System.arraycopy(raisedExceptions, 0,
167: (raisedExceptions = new TypeBinding[raisedCount]), 0,
168: raisedCount);
169: FlowContext traversedContext = this ;
170:
171: while (traversedContext != null) {
172: SubRoutineStatement sub;
173: if (((sub = traversedContext.subroutine()) != null)
174: && sub.isSubRoutineEscaping()) {
175: // traversing a non-returning subroutine means that all unhandled
176: // exceptions will actually never get sent...
177: return;
178: }
179: // filter exceptions that are locally caught from the innermost enclosing
180: // try statement to the outermost ones.
181: if (traversedContext instanceof ExceptionHandlingFlowContext) {
182: ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) traversedContext;
183: ReferenceBinding[] caughtExceptions;
184: if ((caughtExceptions = exceptionContext.handledExceptions) != Binding.NO_EXCEPTIONS) {
185: int caughtCount = caughtExceptions.length;
186: boolean[] locallyCaught = new boolean[raisedCount]; // at most
187:
188: for (int caughtIndex = 0; caughtIndex < caughtCount; caughtIndex++) {
189: ReferenceBinding caughtException = caughtExceptions[caughtIndex];
190: for (int raisedIndex = 0; raisedIndex < raisedCount; raisedIndex++) {
191: TypeBinding raisedException;
192: if ((raisedException = raisedExceptions[raisedIndex]) != null) {
193: int state = caughtException == null ? Scope.EQUAL_OR_MORE_SPECIFIC /* any exception */
194: : Scope.compareTypes(
195: raisedException,
196: caughtException);
197: switch (state) {
198: case Scope.EQUAL_OR_MORE_SPECIFIC:
199: exceptionContext
200: .recordHandlingException(
201: caughtException,
202: flowInfo
203: .unconditionalInits(),
204: raisedException,
205: location,
206: locallyCaught[raisedIndex]);
207: // was already definitely caught ?
208: if (!locallyCaught[raisedIndex]) {
209: locallyCaught[raisedIndex] = true;
210: // remember that this exception has been definitely caught
211: remainingCount--;
212: }
213: break;
214: case Scope.MORE_GENERIC:
215: exceptionContext
216: .recordHandlingException(
217: caughtException,
218: flowInfo
219: .unconditionalInits(),
220: raisedException,
221: location, false);
222: // was not caught already per construction
223: }
224: }
225: }
226: }
227: // remove locally caught exceptions from the remaining ones
228: for (int i = 0; i < raisedCount; i++) {
229: if (locallyCaught[i]) {
230: raisedExceptions[i] = null; // removed from the remaining ones.
231: }
232: }
233: }
234: // method treatment for unchecked exceptions
235: if (exceptionContext.isMethodContext) {
236: for (int i = 0; i < raisedCount; i++) {
237: TypeBinding raisedException;
238: if ((raisedException = raisedExceptions[i]) != null) {
239: if (raisedException
240: .isUncheckedException(false)) {
241: remainingCount--;
242: raisedExceptions[i] = null;
243: }
244: }
245: }
246: // anonymous constructors are allowed to throw any exceptions (their thrown exceptions
247: // clause will be fixed up later as per JLS 8.6).
248: if (exceptionContext.associatedNode instanceof AbstractMethodDeclaration) {
249: AbstractMethodDeclaration method = (AbstractMethodDeclaration) exceptionContext.associatedNode;
250: if (method.isConstructor()
251: && method.binding.declaringClass
252: .isAnonymousType()) {
253:
254: for (int i = 0; i < raisedCount; i++) {
255: TypeBinding raisedException;
256: if ((raisedException = raisedExceptions[i]) != null) {
257: exceptionContext
258: .mergeUnhandledException(raisedException);
259: }
260: }
261: return; // no need to complain, will fix up constructor exceptions
262: }
263: }
264: break; // not handled anywhere, thus jump to error handling
265: }
266: }
267: if (remainingCount == 0)
268: return;
269:
270: traversedContext.recordReturnFrom(flowInfo
271: .unconditionalInits());
272:
273: if (traversedContext instanceof InsideSubRoutineFlowContext) {
274: ASTNode node = traversedContext.associatedNode;
275: if (node instanceof TryStatement) {
276: TryStatement tryStatement = (TryStatement) node;
277: flowInfo
278: .addInitializationsFrom(tryStatement.subRoutineInits); // collect inits
279: }
280: }
281: traversedContext = traversedContext.parent;
282: }
283: // if reaches this point, then there are some remaining unhandled exception types.
284: nextReport: for (int i = 0; i < raisedCount; i++) {
285: TypeBinding exception;
286: if ((exception = raisedExceptions[i]) != null) {
287: // only one complaint if same exception declared to be thrown more than once
288: for (int j = 0; j < i; j++) {
289: if (raisedExceptions[j] == exception)
290: continue nextReport; // already reported
291: }
292: scope.problemReporter().unhandledException(exception,
293: location);
294: }
295: }
296: }
297:
298: public BranchLabel continueLabel() {
299: return null;
300: }
301:
302: /*
303: * lookup through break labels
304: */
305: public FlowContext getTargetContextForBreakLabel(char[] labelName) {
306: FlowContext current = this , lastNonReturningSubRoutine = null;
307: while (current != null) {
308: if (current.isNonReturningContext()) {
309: lastNonReturningSubRoutine = current;
310: }
311: char[] currentLabelName;
312: if (((currentLabelName = current.labelName()) != null)
313: && CharOperation
314: .equals(currentLabelName, labelName)) {
315: ((LabeledStatement) current.associatedNode).bits |= ASTNode.LabelUsed;
316: if (lastNonReturningSubRoutine == null)
317: return current;
318: return lastNonReturningSubRoutine;
319: }
320: current = current.parent;
321: }
322: // not found
323: return null;
324: }
325:
326: /*
327: * lookup through continue labels
328: */
329: public FlowContext getTargetContextForContinueLabel(char[] labelName) {
330: FlowContext current = this ;
331: FlowContext lastContinuable = null;
332: FlowContext lastNonReturningSubRoutine = null;
333:
334: while (current != null) {
335: if (current.isNonReturningContext()) {
336: lastNonReturningSubRoutine = current;
337: } else {
338: if (current.isContinuable()) {
339: lastContinuable = current;
340: }
341: }
342:
343: char[] currentLabelName;
344: if ((currentLabelName = current.labelName()) != null
345: && CharOperation
346: .equals(currentLabelName, labelName)) {
347: ((LabeledStatement) current.associatedNode).bits |= ASTNode.LabelUsed;
348:
349: // matching label found
350: if ((lastContinuable != null)
351: && (current.associatedNode.concreteStatement() == lastContinuable.associatedNode)) {
352:
353: if (lastNonReturningSubRoutine == null)
354: return lastContinuable;
355: return lastNonReturningSubRoutine;
356: }
357: // label is found, but not a continuable location
358: return FlowContext.NotContinuableContext;
359: }
360: current = current.parent;
361: }
362: // not found
363: return null;
364: }
365:
366: /*
367: * lookup a default break through breakable locations
368: */
369: public FlowContext getTargetContextForDefaultBreak() {
370: FlowContext current = this , lastNonReturningSubRoutine = null;
371: while (current != null) {
372: if (current.isNonReturningContext()) {
373: lastNonReturningSubRoutine = current;
374: }
375: if (current.isBreakable() && current.labelName() == null) {
376: if (lastNonReturningSubRoutine == null)
377: return current;
378: return lastNonReturningSubRoutine;
379: }
380: current = current.parent;
381: }
382: // not found
383: return null;
384: }
385:
386: /*
387: * lookup a default continue amongst continuable locations
388: */
389: public FlowContext getTargetContextForDefaultContinue() {
390: FlowContext current = this , lastNonReturningSubRoutine = null;
391: while (current != null) {
392: if (current.isNonReturningContext()) {
393: lastNonReturningSubRoutine = current;
394: }
395: if (current.isContinuable()) {
396: if (lastNonReturningSubRoutine == null)
397: return current;
398: return lastNonReturningSubRoutine;
399: }
400: current = current.parent;
401: }
402: // not found
403: return null;
404: }
405:
406: public String individualToString() {
407: return "Flow context"; //$NON-NLS-1$
408: }
409:
410: public FlowInfo initsOnBreak() {
411: return FlowInfo.DEAD_END;
412: }
413:
414: public UnconditionalFlowInfo initsOnReturn() {
415: return FlowInfo.DEAD_END;
416: }
417:
418: public boolean isBreakable() {
419: return false;
420: }
421:
422: public boolean isContinuable() {
423: return false;
424: }
425:
426: public boolean isNonReturningContext() {
427: return false;
428: }
429:
430: public boolean isSubRoutine() {
431: return false;
432: }
433:
434: public char[] labelName() {
435: return null;
436: }
437:
438: public void recordBreakFrom(FlowInfo flowInfo) {
439: // default implementation: do nothing
440: }
441:
442: public void recordBreakTo(FlowContext targetContext) {
443: // default implementation: do nothing
444: }
445:
446: public void recordContinueFrom(FlowContext innerFlowContext,
447: FlowInfo flowInfo) {
448: // default implementation: do nothing
449: }
450:
451: protected boolean recordFinalAssignment(VariableBinding variable,
452: Reference finalReference) {
453: return true; // keep going
454: }
455:
456: /**
457: * Record a null reference for use by deferred checks. Only looping or
458: * finally contexts really record that information.
459: * @param local the local variable involved in the check
460: * @param expression the expression within which local lays
461: * @param status the status against which the check must be performed; one of
462: * {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL
463: * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL},
464: * {@link #CAN_ONLY_NON_NULL CAN_ONLY_NON_NULL}, potentially
465: * combined with a context indicator (one of {@link #IN_COMPARISON_NULL},
466: * {@link #IN_COMPARISON_NON_NULL}, {@link #IN_ASSIGNMENT} or {@link #IN_INSTANCEOF})
467: */
468: protected void recordNullReference(LocalVariableBinding local,
469: Expression expression, int status) {
470: // default implementation: do nothing
471: }
472:
473: public void recordReturnFrom(UnconditionalFlowInfo flowInfo) {
474: // default implementation: do nothing
475: }
476:
477: public void recordSettingFinal(VariableBinding variable,
478: Reference finalReference, FlowInfo flowInfo) {
479: if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) {
480: // for initialization inside looping statement that effectively loops
481: FlowContext context = this ;
482: while (context != null) {
483: if (!context.recordFinalAssignment(variable,
484: finalReference)) {
485: break; // no need to keep going
486: }
487: context = context.parent;
488: }
489: }
490: }
491:
492: public static final int CAN_ONLY_NULL_NON_NULL = 0x0000,
493: // check against null and non null, with definite values -- comparisons
494: CAN_ONLY_NULL = 0x0001,
495: // check against null, with definite values -- comparisons
496: CAN_ONLY_NON_NULL = 0x0002,
497: // check against non null, with definite values -- comparisons
498: MAY_NULL = 0x0003,
499: // check against null, with potential values -- NPE guard
500: CHECK_MASK = 0x00FF, IN_COMPARISON_NULL = 0x0100,
501: IN_COMPARISON_NON_NULL = 0x0200,
502: // check happened in a comparison
503: IN_ASSIGNMENT = 0x0300,
504: // check happened in an assignment
505: IN_INSTANCEOF = 0x0400,
506: // check happened in an instanceof expression
507: CONTEXT_MASK = ~CHECK_MASK;
508:
509: /**
510: * Record a null reference for use by deferred checks. Only looping or
511: * finally contexts really record that information. The context may
512: * emit an error immediately depending on the status of local against
513: * flowInfo and its nature (only looping of finally contexts defer part
514: * of the checks; nonetheless, contexts that are nested into a looping or a
515: * finally context get affected and delegate some checks to their enclosing
516: * context).
517: * @param scope the scope into which the check is performed
518: * @param local the local variable involved in the check
519: * @param reference the expression within which local lies
520: * @param checkType the status against which the check must be performed; one
521: * of {@link #CAN_ONLY_NULL CAN_ONLY_NULL}, {@link #CAN_ONLY_NULL_NON_NULL
522: * CAN_ONLY_NULL_NON_NULL}, {@link #MAY_NULL MAY_NULL}, potentially
523: * combined with a context indicator (one of {@link #IN_COMPARISON_NULL},
524: * {@link #IN_COMPARISON_NON_NULL}, {@link #IN_ASSIGNMENT} or {@link #IN_INSTANCEOF})
525: * @param flowInfo the flow info at the check point; deferring contexts will
526: * perform supplementary checks against flow info instances that cannot
527: * be known at the time of calling this method (they are influenced by
528: * code that follows the current point)
529: */
530: public void recordUsingNullReference(Scope scope,
531: LocalVariableBinding local, Expression reference,
532: int checkType, FlowInfo flowInfo) {
533: if ((flowInfo.tagBits & FlowInfo.UNREACHABLE) != 0
534: || flowInfo.isDefinitelyUnknown(local)) {
535: return;
536: }
537: switch (checkType) {
538: case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NULL:
539: case CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL:
540: if (flowInfo.isDefinitelyNonNull(local)) {
541: if (checkType == (CAN_ONLY_NULL_NON_NULL | IN_COMPARISON_NON_NULL)) {
542: scope.problemReporter()
543: .localVariableRedundantCheckOnNonNull(
544: local, reference);
545: } else {
546: scope.problemReporter()
547: .localVariableNonNullComparedToNull(local,
548: reference);
549: }
550: return;
551: } else if (flowInfo.cannotBeDefinitelyNullOrNonNull(local)) {
552: return;
553: }
554: case CAN_ONLY_NULL | IN_COMPARISON_NULL:
555: case CAN_ONLY_NULL | IN_COMPARISON_NON_NULL:
556: case CAN_ONLY_NULL | IN_ASSIGNMENT:
557: case CAN_ONLY_NULL | IN_INSTANCEOF:
558: if (flowInfo.isDefinitelyNull(local)) {
559: switch (checkType & CONTEXT_MASK) {
560: case FlowContext.IN_COMPARISON_NULL:
561: scope.problemReporter()
562: .localVariableRedundantCheckOnNull(local,
563: reference);
564: return;
565: case FlowContext.IN_COMPARISON_NON_NULL:
566: scope.problemReporter()
567: .localVariableNullComparedToNonNull(local,
568: reference);
569: return;
570: case FlowContext.IN_ASSIGNMENT:
571: scope.problemReporter()
572: .localVariableRedundantNullAssignment(
573: local, reference);
574: return;
575: case FlowContext.IN_INSTANCEOF:
576: scope.problemReporter()
577: .localVariableNullInstanceof(local,
578: reference);
579: return;
580: }
581: } else if (flowInfo.cannotBeDefinitelyNullOrNonNull(local)) {
582: return;
583: }
584: break;
585: case MAY_NULL:
586: if (flowInfo.isDefinitelyNull(local)) {
587: scope.problemReporter().localVariableNullReference(
588: local, reference);
589: return;
590: }
591: if (flowInfo.isPotentiallyNull(local)) {
592: scope.problemReporter()
593: .localVariablePotentialNullReference(local,
594: reference);
595: return;
596: }
597: break;
598: default:
599: // never happens
600: }
601: if (this .parent != null) {
602: this .parent.recordUsingNullReference(scope, local,
603: reference, checkType, flowInfo);
604: }
605: }
606:
607: void removeFinalAssignmentIfAny(Reference reference) {
608: // default implementation: do nothing
609: }
610:
611: public SubRoutineStatement subroutine() {
612: return null;
613: }
614:
615: public String toString() {
616: StringBuffer buffer = new StringBuffer();
617: FlowContext current = this ;
618: int parentsCount = 0;
619: while ((current = current.parent) != null) {
620: parentsCount++;
621: }
622: FlowContext[] parents = new FlowContext[parentsCount + 1];
623: current = this ;
624: int index = parentsCount;
625: while (index >= 0) {
626: parents[index--] = current;
627: current = current.parent;
628: }
629: for (int i = 0; i < parentsCount; i++) {
630: for (int j = 0; j < i; j++)
631: buffer.append('\t');
632: buffer.append(parents[i].individualToString()).append('\n');
633: }
634: buffer.append('*');
635: for (int j = 0; j < parentsCount + 1; j++)
636: buffer.append('\t');
637: buffer.append(individualToString()).append('\n');
638: return buffer.toString();
639: }
640: }
|