1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.ast;
13 import org.eclipse.jdt.internal.compiler.ASTVisitor;
14 import org.eclipse.jdt.internal.compiler.codegen.*;
15 import org.eclipse.jdt.internal.compiler.flow.*;
16 import org.eclipse.jdt.internal.compiler.lookup.*;
18 public class TryStatement extends SubRoutineStatement {
20 public Block tryBlock;
21 public Block[] catchBlocks;
22 public Argument[] catchArguments;
23 public Block finallyBlock;
26 private boolean isSubRoutineEscaping = false;
27 public UnconditionalFlowInfo subRoutineInits;
29 // should rename into subRoutineComplete to be set to false by default
31 ReferenceBinding[] caughtExceptionTypes;
34 public int[] preserveExceptionHandler;
36 Label subRoutineStartLabel;
37 public LocalVariableBinding anyExceptionVariable,
38 returnAddressVariable,
41 public final static char[] SecretReturnName = " returnAddress".toCharArray(); //$NON-NLS-1$
42 public final static char[] SecretAnyHandlerName = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$
43 public static final char[] SecretLocalDeclarationName = " returnValue".toCharArray(); //$NON-NLS-1$
45 // for local variables table attributes
46 int preTryInitStateIndex = -1;
47 int mergedInitStateIndex = -1;
49 public FlowInfo analyseCode(
50 BlockScope currentScope,
51 FlowContext flowContext,
54 // Consider the try block and catch block so as to compute the intersection of initializations and
55 // the minimum exit relative depth amongst all of them. Then consider the subroutine, and append its
56 // initialization to the try/catch ones, if the subroutine completes normally. If the subroutine does not
57 // complete, then only keep this result for the rest of the analysis
59 // process the finally block (subroutine) - create a context for the subroutine
61 preTryInitStateIndex =
62 currentScope.methodScope().recordInitializationStates(flowInfo);
64 if (anyExceptionVariable != null) {
65 anyExceptionVariable.useFlag = LocalVariableBinding.USED;
67 if (returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused
68 returnAddressVariable.useFlag = LocalVariableBinding.USED;
70 InsideSubRoutineFlowContext insideSubContext;
71 FinallyFlowContext finallyContext;
72 UnconditionalFlowInfo subInfo;
73 if (subRoutineStartLabel == null) {
75 insideSubContext = null;
76 finallyContext = null;
79 // analyse finally block first
80 insideSubContext = new InsideSubRoutineFlowContext(flowContext, this);
85 finallyContext = new FinallyFlowContext(flowContext, finallyBlock),
87 .unconditionalInits();
88 if (subInfo == FlowInfo.DEAD_END) {
89 isSubRoutineEscaping = true;
90 scope.problemReporter().finallyMustCompleteNormally(finallyBlock);
92 this.subRoutineInits = subInfo;
94 // process the try block in a context handling the local exceptions.
95 ExceptionHandlingFlowContext handlingContext =
96 new ExceptionHandlingFlowContext(
97 insideSubContext == null ? flowContext : insideSubContext,
101 flowInfo.unconditionalInits());
104 if (tryBlock.isEmptyBlock()) {
106 tryBlockExit = false;
108 tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
109 tryBlockExit = !tryInfo.isReachable();
112 // check unreachable catch blocks
113 handlingContext.complainIfUnusedExceptionHandlers(scope, this);
115 // process the catch blocks - computing the minimal exit depth amongst try/catch
116 if (catchArguments != null) {
118 catchExits = new boolean[catchCount = catchBlocks.length];
119 for (int i = 0; i < catchCount; i++) {
120 // keep track of the inits that could potentially have led to this exception handler (for final assignments diagnosis)
124 .unconditionalInits()
125 .addPotentialInitializationsFrom(
126 handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits())
127 .addPotentialInitializationsFrom(tryInfo.unconditionalInits())
128 .addPotentialInitializationsFrom(handlingContext.initsOnReturn);
130 // catch var is always set
131 catchInfo.markAsDefinitelyAssigned(catchArguments[i].binding);
133 "If we are about to consider an unchecked exception handler, potential inits may have occured inside
134 the try block that need to be detected , e.g.
135 try { x = 1; throwSomething();} catch(Exception e){ x = 2} "
136 "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index])
137 ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]."
139 // TODO (philippe) should only tag as unreachable if the catchblock cannot be reached?
140 //??? if (!handlingContext.initsOnException(caughtExceptionTypes[i]).isReachable()){
141 if (tryBlock.statements == null) {
142 catchInfo.setReachMode(FlowInfo.UNREACHABLE);
145 catchBlocks[i].analyseCode(
147 insideSubContext == null ? flowContext : insideSubContext,
149 catchExits[i] = !catchInfo.isReachable();
150 tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
153 if (subRoutineStartLabel == null) {
154 mergedInitStateIndex =
155 currentScope.methodScope().recordInitializationStates(tryInfo);
160 // we also need to check potential multiple assignments of final variables inside the finally block
161 // need to include potential inits from returns inside the try/catch parts - 1GK2AOF
162 finallyContext.complainOnRedundantFinalAssignments(
163 tryInfo.isReachable()
164 ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn))
165 : insideSubContext.initsOnReturn,
167 if (subInfo == FlowInfo.DEAD_END) {
168 mergedInitStateIndex =
169 currentScope.methodScope().recordInitializationStates(subInfo);
172 FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo);
173 mergedInitStateIndex =
174 currentScope.methodScope().recordInitializationStates(mergedInfo);
179 public boolean isSubRoutineEscaping() {
181 return isSubRoutineEscaping;
185 * Try statement code generation with or without jsr bytecode use
186 * post 1.5 target level, cannot use jsr bytecode, must instead inline finally block
187 * returnAddress is only allocated if jsr is allowed
189 public void generateCode(BlockScope currentScope, CodeStream codeStream) {
191 if ((bits & IsReachableMASK) == 0) {
194 // in case the labels needs to be reinitialized
195 // when the code generation is restarted in wide mode
196 if (this.anyExceptionLabelsCount > 0) {
197 this.anyExceptionLabels = NO_EXCEPTION_HANDLER;
198 this.anyExceptionLabelsCount = 0;
200 int pc = codeStream.position;
201 final int NO_FINALLY = 0; // no finally block
202 final int FINALLY_SUBROUTINE = 1; // finally is generated as a subroutine (using jsr/ret bytecodes)
203 final int FINALLY_DOES_NOT_COMPLETE = 2; // non returning finally is optimized with only one instance of finally block
204 final int FINALLY_MUST_BE_INLINED = 3; // finally block must be inlined since cannot use jsr/ret bytecodes >1.5
206 if (subRoutineStartLabel == null) {
207 finallyMode = NO_FINALLY;
209 if (this.isSubRoutineEscaping) {
210 finallyMode = FINALLY_DOES_NOT_COMPLETE;
211 } else if (scope.environment().options.inlineJsrBytecode) {
212 finallyMode = FINALLY_MUST_BE_INLINED;
214 finallyMode = FINALLY_SUBROUTINE;
217 boolean requiresNaturalExit = false;
218 // preparing exception labels
220 ExceptionLabel[] exceptionLabels =
221 new ExceptionLabel[maxCatches =
222 catchArguments == null ? 0 : catchArguments.length];
223 for (int i = 0; i < maxCatches; i++) {
224 exceptionLabels[i] = new ExceptionLabel(codeStream, catchArguments[i].binding.type);
226 if (subRoutineStartLabel != null) {
227 subRoutineStartLabel.initialize(codeStream);
228 this.enterAnyExceptionHandler(codeStream);
230 // generate the try block
231 tryBlock.generateCode(scope, codeStream);
232 boolean tryBlockHasSomeCode = codeStream.position != pc;
233 // flag telling if some bytecodes were issued inside the try block
235 // place end positions of user-defined exception labels
236 if (tryBlockHasSomeCode) {
237 // natural exit may require subroutine invocation (if finally != null)
238 Label naturalExitLabel = new Label(codeStream);
240 int position = codeStream.position;
241 switch(finallyMode) {
242 case FINALLY_SUBROUTINE :
243 case FINALLY_MUST_BE_INLINED :
244 requiresNaturalExit = true;
247 codeStream.goto_(naturalExitLabel);
249 case FINALLY_DOES_NOT_COMPLETE :
250 codeStream.goto_(subRoutineStartLabel);
253 codeStream.updateLastRecordedEndPC(position);
254 //goto is tagged as part of the try block
256 for (int i = 0; i < maxCatches; i++) {
257 exceptionLabels[i].placeEnd();
259 /* generate sequence of handler, all starting by storing the TOS (exception
260 thrown) into their own catch variables, the one specified in the source
261 that must denote the handled exception.
263 if (catchArguments == null) {
264 this.exitAnyExceptionHandler();
266 for (int i = 0; i < maxCatches; i++) {
267 // May loose some local variable initializations : affecting the local variable attributes
268 if (preTryInitStateIndex != -1) {
269 codeStream.removeNotDefinitelyAssignedVariables(
271 preTryInitStateIndex);
273 exceptionLabels[i].place();
274 codeStream.incrStackSize(1);
275 // optimizing the case where the exception variable is not actually used
276 LocalVariableBinding catchVar;
277 int varPC = codeStream.position;
278 if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) {
279 codeStream.store(catchVar, false);
280 catchVar.recordInitializationStartPC(codeStream.position);
281 codeStream.addVisibleLocalVariable(catchVar);
285 codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart);
286 // Keep track of the pcs at diverging point for computing the local attribute
287 // since not passing the catchScope, the block generation will exitUserScope(catchScope)
288 catchBlocks[i].generateCode(scope, codeStream);
290 if (i == maxCatches - 1) {
291 this.exitAnyExceptionHandler();
293 if (!catchExits[i]) {
294 switch(finallyMode) {
295 case FINALLY_SUBROUTINE :
296 case FINALLY_MUST_BE_INLINED :
297 requiresNaturalExit = true;
300 codeStream.goto_(naturalExitLabel);
302 case FINALLY_DOES_NOT_COMPLETE :
303 codeStream.goto_(subRoutineStartLabel);
309 // extra handler for trailing natural exit (will be fixed up later on when natural exit is generated below)
310 ExceptionLabel naturalExitExceptionHandler =
311 finallyMode == FINALLY_SUBROUTINE && requiresNaturalExit ? this.enterAnyExceptionHandler(codeStream) : null;
313 // addition of a special handler so as to ensure that any uncaught exception (or exception thrown
314 // inside catch blocks) will run the finally block
315 int finallySequenceStartPC = codeStream.position;
316 if (subRoutineStartLabel != null) {
317 // the additional handler is doing: jsr finallyBlock and rethrow TOS-exception
318 this.placeAllAnyExceptionHandlers();
320 if (preTryInitStateIndex != -1) {
321 // reset initialization state, as for a normal catch block
322 codeStream.removeNotDefinitelyAssignedVariables(
324 preTryInitStateIndex);
327 codeStream.incrStackSize(1);
328 switch(finallyMode) {
330 case FINALLY_SUBROUTINE :
331 codeStream.store(anyExceptionVariable, false);
332 codeStream.jsr(subRoutineStartLabel);
333 codeStream.load(anyExceptionVariable);
335 subRoutineStartLabel.place();
336 codeStream.incrStackSize(1);
337 codeStream.store(returnAddressVariable, false);
338 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
339 finallyBlock.generateCode(scope, codeStream);
340 int position = codeStream.position;
341 codeStream.ret(returnAddressVariable.resolvedPosition);
342 codeStream.updateLastRecordedEndPC(position);
343 codeStream.recordPositionsFrom(
345 finallyBlock.sourceEnd);
346 // the ret bytecode is part of the subroutine
349 case FINALLY_MUST_BE_INLINED :
350 codeStream.store(anyExceptionVariable, false);
351 this.finallyBlock.generateCode(currentScope, codeStream);
352 codeStream.load(anyExceptionVariable);
354 subRoutineStartLabel.place();
355 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
358 case FINALLY_DOES_NOT_COMPLETE :
360 subRoutineStartLabel.place();
361 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
362 finallyBlock.generateCode(scope, codeStream);
365 // will naturally fall into subsequent code after subroutine invocation
366 naturalExitLabel.place();
367 if (requiresNaturalExit) {
368 switch(finallyMode) {
370 case FINALLY_SUBROUTINE :
371 int position = codeStream.position;
372 // fix up natural exit handler
373 naturalExitExceptionHandler.placeStart();
374 codeStream.jsr(subRoutineStartLabel);
375 naturalExitExceptionHandler.placeEnd();
376 codeStream.recordPositionsFrom(
378 finallyBlock.sourceStart);
381 case FINALLY_MUST_BE_INLINED :
382 // May loose some local variable initializations : affecting the local variable attributes
383 // needed since any exception handler got inlined subroutine
384 if (preTryInitStateIndex != -1) {
385 codeStream.removeNotDefinitelyAssignedVariables(
387 preTryInitStateIndex);
389 // entire sequence for finally is associated to finally block
390 finallyBlock.generateCode(scope, codeStream);
393 case FINALLY_DOES_NOT_COMPLETE :
398 // no subroutine, simply position end label (natural exit == end)
399 naturalExitLabel.place();
402 // try block had no effect, only generate the body of the finally block if any
403 if (subRoutineStartLabel != null) {
404 finallyBlock.generateCode(scope, codeStream);
407 // May loose some local variable initializations : affecting the local variable attributes
408 if (mergedInitStateIndex != -1) {
409 codeStream.removeNotDefinitelyAssignedVariables(
411 mergedInitStateIndex);
412 codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
414 codeStream.recordPositionsFrom(pc, this.sourceStart);
418 * @see org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement#generateSubRoutineInvocation(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.codegen.CodeStream)
420 public void generateSubRoutineInvocation(
421 BlockScope currentScope,
422 CodeStream codeStream) {
424 if (this.isSubRoutineEscaping) {
425 codeStream.goto_(this.subRoutineStartLabel);
427 if (currentScope.environment().options.inlineJsrBytecode) {
428 // cannot use jsr bytecode, then simply inline the subroutine
429 this.finallyBlock.generateCode(currentScope, codeStream);
431 // classic subroutine invocation, distinguish case of non-returning subroutine
432 codeStream.jsr(this.subRoutineStartLabel);
437 public StringBuffer printStatement(int indent, StringBuffer output) {
438 printIndent(indent, output).append("try \n"); //$NON-NLS-1$
439 tryBlock.printStatement(indent + 1, output); //$NON-NLS-1$
442 if (catchBlocks != null)
443 for (int i = 0; i < catchBlocks.length; i++) {
445 printIndent(indent, output).append("catch ("); //$NON-NLS-1$
446 catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$
447 catchBlocks[i].printStatement(indent + 1, output);
450 if (finallyBlock != null) {
452 printIndent(indent, output).append("finally\n"); //$NON-NLS-1$
453 finallyBlock.printStatement(indent + 1, output);
459 public void resolve(BlockScope upperScope) {
461 // special scope for secret locals optimization.
462 this.scope = new BlockScope(upperScope);
464 BlockScope tryScope = new BlockScope(scope);
465 BlockScope finallyScope = null;
467 if (finallyBlock != null) {
468 if (finallyBlock.isEmptyBlock()) {
469 if ((finallyBlock.bits & UndocumentedEmptyBlockMASK) != 0) {
470 scope.problemReporter().undocumentedEmptyBlock(finallyBlock.sourceStart, finallyBlock.sourceEnd);
473 finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope
475 // provision for returning and forcing the finally block to run
476 MethodScope methodScope = scope.methodScope();
478 // the type does not matter as long as it is not a base type
479 if (!upperScope.environment().options.inlineJsrBytecode) {
480 this.returnAddressVariable =
481 new LocalVariableBinding(SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false);
482 finallyScope.addLocalVariable(returnAddressVariable);
483 this.returnAddressVariable.constant = NotAConstant; // not inlinable
485 this.subRoutineStartLabel = new Label();
487 this.anyExceptionVariable =
488 new LocalVariableBinding(SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false);
489 finallyScope.addLocalVariable(this.anyExceptionVariable);
490 this.anyExceptionVariable.constant = NotAConstant; // not inlinable
492 if (!methodScope.isInsideInitializer()) {
493 MethodBinding methodBinding =
494 ((AbstractMethodDeclaration) methodScope.referenceContext).binding;
495 if (methodBinding != null) {
496 TypeBinding methodReturnType = methodBinding.returnType;
497 if (methodReturnType.id != T_void) {
498 this.secretReturnValue =
499 new LocalVariableBinding(
500 SecretLocalDeclarationName,
504 finallyScope.addLocalVariable(this.secretReturnValue);
505 this.secretReturnValue.constant = NotAConstant; // not inlinable
509 finallyBlock.resolveUsing(finallyScope);
510 // force the finally scope to have variable positions shifted after its try scope and catch ones
511 finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length+1];
512 finallyScope.shiftScopes[0] = tryScope;
515 this.tryBlock.resolveUsing(tryScope);
517 // arguments type are checked against JavaLangThrowable in resolveForCatch(..)
518 if (this.catchBlocks != null) {
519 int length = this.catchArguments.length;
520 TypeBinding[] argumentTypes = new TypeBinding[length];
521 for (int i = 0; i < length; i++) {
522 BlockScope catchScope = new BlockScope(scope);
523 if (finallyScope != null){
524 finallyScope.shiftScopes[i+1] = catchScope;
526 // side effect on catchScope in resolveForCatch(..)
527 if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null)
529 catchBlocks[i].resolveUsing(catchScope);
532 // Verify that the catch clause are ordered in the right way:
533 // more specialized first.
534 this.caughtExceptionTypes = new ReferenceBinding[length];
535 for (int i = 0; i < length; i++) {
536 caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i];
537 for (int j = 0; j < i; j++) {
538 if (caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) {
539 scope.problemReporter().wrongSequenceOfExceptionTypesError(this, caughtExceptionTypes[i], i, argumentTypes[j]);
544 caughtExceptionTypes = new ReferenceBinding[0];
547 if (finallyScope != null){
548 // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes.
549 // the shifting is necessary to achieve no overlay in between the finally scope and its
550 // sibling in term of local variable positions.
551 this.scope.addSubscope(finallyScope);
555 public void traverse(
557 BlockScope blockScope) {
559 if (visitor.visit(this, blockScope)) {
560 tryBlock.traverse(visitor, scope);
561 if (catchArguments != null) {
562 for (int i = 0, max = catchBlocks.length; i < max; i++) {
563 catchArguments[i].traverse(visitor, scope);
564 catchBlocks[i].traverse(visitor, scope);
567 if (finallyBlock != null)
568 finallyBlock.traverse(visitor, scope);
570 visitor.endVisit(this, blockScope);