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),
86 flowInfo.copy().unconditionalInits().discardNullRelatedInitializations())
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 LocalVariableBinding catchArg = catchArguments[i].binding;
132 FlowContext catchContext = insideSubContext == null ? flowContext : insideSubContext;
133 catchInfo.markAsDefinitelyAssigned(catchArg);
134 catchInfo.markAsDefinitelyNonNull(catchArg);
136 "If we are about to consider an unchecked exception handler, potential inits may have occured inside
137 the try block that need to be detected , e.g.
138 try { x = 1; throwSomething();} catch(Exception e){ x = 2} "
139 "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index])
140 ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]."
142 if (tryBlock.statements == null) {
143 catchInfo.setReachMode(FlowInfo.UNREACHABLE);
146 catchBlocks[i].analyseCode(
150 catchExits[i] = !catchInfo.isReachable();
151 tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
154 if (subRoutineStartLabel == null) {
155 mergedInitStateIndex =
156 currentScope.methodScope().recordInitializationStates(tryInfo);
161 // we also need to check potential multiple assignments of final variables inside the finally block
162 // need to include potential inits from returns inside the try/catch parts - 1GK2AOF
163 finallyContext.complainOnDeferredChecks(
164 tryInfo.isReachable()
165 ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn))
166 : insideSubContext.initsOnReturn,
168 if (subInfo == FlowInfo.DEAD_END) {
169 mergedInitStateIndex =
170 currentScope.methodScope().recordInitializationStates(subInfo);
173 FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo);
174 mergedInitStateIndex =
175 currentScope.methodScope().recordInitializationStates(mergedInfo);
180 public boolean isSubRoutineEscaping() {
182 return isSubRoutineEscaping;
186 * Try statement code generation with or without jsr bytecode use
187 * post 1.5 target level, cannot use jsr bytecode, must instead inline finally block
188 * returnAddress is only allocated if jsr is allowed
190 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 for (int i = 0; i < maxCatches; i++) {
265 // May loose some local variable initializations : affecting the local variable attributes
266 if (preTryInitStateIndex != -1) {
267 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex);
269 exceptionLabels[i].place();
270 codeStream.incrStackSize(1);
271 // optimizing the case where the exception variable is not actually used
272 LocalVariableBinding catchVar;
273 int varPC = codeStream.position;
274 if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) {
275 codeStream.store(catchVar, false);
276 catchVar.recordInitializationStartPC(codeStream.position);
277 codeStream.addVisibleLocalVariable(catchVar);
281 codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart);
282 // Keep track of the pcs at diverging point for computing the local attribute
283 // since not passing the catchScope, the block generation will exitUserScope(catchScope)
284 catchBlocks[i].generateCode(scope, codeStream);
285 if (!catchExits[i]) {
286 switch(finallyMode) {
287 case FINALLY_SUBROUTINE :
288 case FINALLY_MUST_BE_INLINED :
289 requiresNaturalExit = true;
292 codeStream.goto_(naturalExitLabel);
294 case FINALLY_DOES_NOT_COMPLETE :
295 codeStream.goto_(subRoutineStartLabel);
301 this.exitAnyExceptionHandler();
302 // extra handler for trailing natural exit (will be fixed up later on when natural exit is generated below)
303 ExceptionLabel naturalExitExceptionHandler =
304 finallyMode == FINALLY_SUBROUTINE && requiresNaturalExit ? new ExceptionLabel(codeStream, null) : null;
306 // addition of a special handler so as to ensure that any uncaught exception (or exception thrown
307 // inside catch blocks) will run the finally block
308 int finallySequenceStartPC = codeStream.position;
309 if (subRoutineStartLabel != null) {
310 this.placeAllAnyExceptionHandlers();
311 if (naturalExitExceptionHandler != null) naturalExitExceptionHandler.place();
313 if (preTryInitStateIndex != -1) {
314 // reset initialization state, as for a normal catch block
315 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex);
318 codeStream.incrStackSize(1);
319 switch(finallyMode) {
320 case FINALLY_SUBROUTINE :
321 codeStream.store(anyExceptionVariable, false);
322 codeStream.jsr(subRoutineStartLabel);
323 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
324 int position = codeStream.position;
325 codeStream.load(anyExceptionVariable);
327 codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd);
328 subRoutineStartLabel.place();
329 codeStream.incrStackSize(1);
330 position = codeStream.position;
331 codeStream.store(returnAddressVariable, false);
332 codeStream.recordPositionsFrom(position, finallyBlock.sourceStart);
333 finallyBlock.generateCode(scope, codeStream);
334 position = codeStream.position;
335 codeStream.ret(returnAddressVariable.resolvedPosition);
336 // codeStream.updateLastRecordedEndPC(position);
337 codeStream.recordPositionsFrom(
339 finallyBlock.sourceEnd);
340 // the ret bytecode is part of the subroutine
342 case FINALLY_MUST_BE_INLINED :
343 codeStream.store(anyExceptionVariable, false);
344 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
345 this.finallyBlock.generateCode(currentScope, codeStream);
346 position = codeStream.position;
347 codeStream.load(anyExceptionVariable);
349 subRoutineStartLabel.place();
350 codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd);
352 case FINALLY_DOES_NOT_COMPLETE :
354 subRoutineStartLabel.place();
355 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart);
356 finallyBlock.generateCode(scope, codeStream);
359 // will naturally fall into subsequent code after subroutine invocation
360 naturalExitLabel.place();
361 if (requiresNaturalExit) {
362 switch(finallyMode) {
363 case FINALLY_SUBROUTINE :
364 int position = codeStream.position;
365 // fix up natural exit handler
366 naturalExitExceptionHandler.placeStart();
367 codeStream.jsr(subRoutineStartLabel);
368 naturalExitExceptionHandler.placeEnd();
369 codeStream.recordPositionsFrom(
371 finallyBlock.sourceEnd);
373 case FINALLY_MUST_BE_INLINED :
374 // May loose some local variable initializations : affecting the local variable attributes
375 // needed since any exception handler got inlined subroutine
376 if (preTryInitStateIndex != -1) {
377 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex);
379 // entire sequence for finally is associated to finally block
380 finallyBlock.generateCode(scope, codeStream);
382 case FINALLY_DOES_NOT_COMPLETE :
387 // no subroutine, simply position end label (natural exit == end)
388 naturalExitLabel.place();
391 // try block had no effect, only generate the body of the finally block if any
392 if (subRoutineStartLabel != null) {
393 finallyBlock.generateCode(scope, codeStream);
396 // May loose some local variable initializations : affecting the local variable attributes
397 if (mergedInitStateIndex != -1) {
398 codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
399 codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
401 codeStream.recordPositionsFrom(pc, this.sourceStart);
405 * @see org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement#generateSubRoutineInvocation(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.codegen.CodeStream)
407 public void generateSubRoutineInvocation(
408 BlockScope currentScope,
409 CodeStream codeStream) {
411 if (this.isSubRoutineEscaping) {
412 codeStream.goto_(this.subRoutineStartLabel);
414 if (currentScope.environment().options.inlineJsrBytecode) {
415 // cannot use jsr bytecode, then simply inline the subroutine
416 this.finallyBlock.generateCode(currentScope, codeStream);
418 // classic subroutine invocation, distinguish case of non-returning subroutine
419 codeStream.jsr(this.subRoutineStartLabel);
424 public StringBuffer printStatement(int indent, StringBuffer output) {
425 printIndent(indent, output).append("try \n"); //$NON-NLS-1$
426 tryBlock.printStatement(indent + 1, output); //$NON-NLS-1$
429 if (catchBlocks != null)
430 for (int i = 0; i < catchBlocks.length; i++) {
432 printIndent(indent, output).append("catch ("); //$NON-NLS-1$
433 catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$
434 catchBlocks[i].printStatement(indent + 1, output);
437 if (finallyBlock != null) {
439 printIndent(indent, output).append("finally\n"); //$NON-NLS-1$
440 finallyBlock.printStatement(indent + 1, output);
446 public void resolve(BlockScope upperScope) {
448 // special scope for secret locals optimization.
449 this.scope = new BlockScope(upperScope);
451 BlockScope tryScope = new BlockScope(scope);
452 BlockScope finallyScope = null;
454 if (finallyBlock != null) {
455 if (finallyBlock.isEmptyBlock()) {
456 if ((finallyBlock.bits & UndocumentedEmptyBlockMASK) != 0) {
457 scope.problemReporter().undocumentedEmptyBlock(finallyBlock.sourceStart, finallyBlock.sourceEnd);
460 finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope
462 // provision for returning and forcing the finally block to run
463 MethodScope methodScope = scope.methodScope();
465 // the type does not matter as long as it is not a base type
466 if (!upperScope.environment().options.inlineJsrBytecode) {
467 this.returnAddressVariable =
468 new LocalVariableBinding(SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false);
469 finallyScope.addLocalVariable(returnAddressVariable);
470 this.returnAddressVariable.setConstant(NotAConstant); // not inlinable
472 this.subRoutineStartLabel = new Label();
474 this.anyExceptionVariable =
475 new LocalVariableBinding(SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false);
476 finallyScope.addLocalVariable(this.anyExceptionVariable);
477 this.anyExceptionVariable.setConstant(NotAConstant); // not inlinable
479 if (!methodScope.isInsideInitializer()) {
480 MethodBinding methodBinding =
481 ((AbstractMethodDeclaration) methodScope.referenceContext).binding;
482 if (methodBinding != null) {
483 TypeBinding methodReturnType = methodBinding.returnType;
484 if (methodReturnType.id != T_void) {
485 this.secretReturnValue =
486 new LocalVariableBinding(
487 SecretLocalDeclarationName,
491 finallyScope.addLocalVariable(this.secretReturnValue);
492 this.secretReturnValue.setConstant(NotAConstant); // not inlinable
496 finallyBlock.resolveUsing(finallyScope);
497 // force the finally scope to have variable positions shifted after its try scope and catch ones
498 finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length+1];
499 finallyScope.shiftScopes[0] = tryScope;
502 this.tryBlock.resolveUsing(tryScope);
504 // arguments type are checked against JavaLangThrowable in resolveForCatch(..)
505 if (this.catchBlocks != null) {
506 int length = this.catchArguments.length;
507 TypeBinding[] argumentTypes = new TypeBinding[length];
508 boolean catchHasError = false;
509 for (int i = 0; i < length; i++) {
510 BlockScope catchScope = new BlockScope(scope);
511 if (finallyScope != null){
512 finallyScope.shiftScopes[i+1] = catchScope;
514 // side effect on catchScope in resolveForCatch(..)
515 if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null) {
516 catchHasError = true;
518 catchBlocks[i].resolveUsing(catchScope);
523 // Verify that the catch clause are ordered in the right way:
524 // more specialized first.
525 this.caughtExceptionTypes = new ReferenceBinding[length];
526 for (int i = 0; i < length; i++) {
527 caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i];
528 for (int j = 0; j < i; j++) {
529 if (caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) {
530 scope.problemReporter().wrongSequenceOfExceptionTypesError(this, caughtExceptionTypes[i], i, argumentTypes[j]);
535 caughtExceptionTypes = new ReferenceBinding[0];
538 if (finallyScope != null){
539 // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes.
540 // the shifting is necessary to achieve no overlay in between the finally scope and its
541 // sibling in term of local variable positions.
542 this.scope.addSubscope(finallyScope);
546 public void traverse(
548 BlockScope blockScope) {
550 if (visitor.visit(this, blockScope)) {
551 tryBlock.traverse(visitor, scope);
552 if (catchArguments != null) {
553 for (int i = 0, max = catchBlocks.length; i < max; i++) {
554 catchArguments[i].traverse(visitor, scope);
555 catchBlocks[i].traverse(visitor, scope);
558 if (finallyBlock != null)
559 finallyBlock.traverse(visitor, scope);
561 visitor.endVisit(this, blockScope);