/******************************************************************************* * 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.ast; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.codegen.*; import org.eclipse.jdt.internal.compiler.flow.*; import org.eclipse.jdt.internal.compiler.lookup.*; public class TryStatement extends SubRoutineStatement { public Block tryBlock; public Block[] catchBlocks; public Argument[] catchArguments; public Block finallyBlock; BlockScope scope; private boolean isSubRoutineEscaping = false; public UnconditionalFlowInfo subRoutineInits; // should rename into subRoutineComplete to be set to false by default ReferenceBinding[] caughtExceptionTypes; boolean tryBlockExit; boolean[] catchExits; public int[] preserveExceptionHandler; Label subRoutineStartLabel; public LocalVariableBinding anyExceptionVariable, returnAddressVariable, secretReturnValue; public final static char[] SecretReturnName = " returnAddress".toCharArray(); //$NON-NLS-1$ public final static char[] SecretAnyHandlerName = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$ public static final char[] SecretLocalDeclarationName = " returnValue".toCharArray(); //$NON-NLS-1$ // for local variables table attributes int preTryInitStateIndex = -1; int mergedInitStateIndex = -1; public FlowInfo analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { // Consider the try block and catch block so as to compute the intersection of initializations and // the minimum exit relative depth amongst all of them. Then consider the subroutine, and append its // initialization to the try/catch ones, if the subroutine completes normally. If the subroutine does not // complete, then only keep this result for the rest of the analysis // process the finally block (subroutine) - create a context for the subroutine preTryInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); if (anyExceptionVariable != null) { anyExceptionVariable.useFlag = LocalVariableBinding.USED; } if (returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused returnAddressVariable.useFlag = LocalVariableBinding.USED; } InsideSubRoutineFlowContext insideSubContext; FinallyFlowContext finallyContext; UnconditionalFlowInfo subInfo; if (subRoutineStartLabel == null) { // no finally block insideSubContext = null; finallyContext = null; subInfo = null; } else { // analyse finally block first insideSubContext = new InsideSubRoutineFlowContext(flowContext, this); subInfo = finallyBlock .analyseCode( currentScope, finallyContext = new FinallyFlowContext(flowContext, finallyBlock), flowInfo.copy()) .unconditionalInits(); if (subInfo == FlowInfo.DEAD_END) { isSubRoutineEscaping = true; scope.problemReporter().finallyMustCompleteNormally(finallyBlock); } this.subRoutineInits = subInfo; } // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext( insideSubContext == null ? flowContext : insideSubContext, tryBlock, caughtExceptionTypes, scope, flowInfo.unconditionalInits()); FlowInfo tryInfo; if (tryBlock.isEmptyBlock()) { tryInfo = flowInfo; tryBlockExit = false; } else { tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); tryBlockExit = !tryInfo.isReachable(); } // check unreachable catch blocks handlingContext.complainIfUnusedExceptionHandlers(scope, this); // process the catch blocks - computing the minimal exit depth amongst try/catch if (catchArguments != null) { int catchCount; catchExits = new boolean[catchCount = catchBlocks.length]; for (int i = 0; i < catchCount; i++) { // keep track of the inits that could potentially have led to this exception handler (for final assignments diagnosis) FlowInfo catchInfo = flowInfo .copy() .unconditionalInits() .addPotentialInitializationsFrom( handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits()) .addPotentialInitializationsFrom(tryInfo.unconditionalInits()) .addPotentialInitializationsFrom(handlingContext.initsOnReturn); // catch var is always set catchInfo.markAsDefinitelyAssigned(catchArguments[i].binding); /* "If we are about to consider an unchecked exception handler, potential inits may have occured inside the try block that need to be detected , e.g. try { x = 1; throwSomething();} catch(Exception e){ x = 2} " "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index]) ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]." */ // TODO (philippe) should only tag as unreachable if the catchblock cannot be reached? //??? if (!handlingContext.initsOnException(caughtExceptionTypes[i]).isReachable()){ if (tryBlock.statements == null) { catchInfo.setReachMode(FlowInfo.UNREACHABLE); } catchInfo = catchBlocks[i].analyseCode( currentScope, insideSubContext == null ? flowContext : insideSubContext, catchInfo); catchExits[i] = !catchInfo.isReachable(); tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits()); } } if (subRoutineStartLabel == null) { mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(tryInfo); return tryInfo; } // we also need to check potential multiple assignments of final variables inside the finally block // need to include potential inits from returns inside the try/catch parts - 1GK2AOF finallyContext.complainOnRedundantFinalAssignments( tryInfo.isReachable() ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn)) : insideSubContext.initsOnReturn, currentScope); if (subInfo == FlowInfo.DEAD_END) { mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(subInfo); return subInfo; } else { FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo); mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } } public boolean isSubRoutineEscaping() { return isSubRoutineEscaping; } /** * Try statement code generation with or without jsr bytecode use * post 1.5 target level, cannot use jsr bytecode, must instead inline finally block * returnAddress is only allocated if jsr is allowed */ public void generateCode(BlockScope currentScope, CodeStream codeStream) { if ((bits & IsReachableMASK) == 0) { return; } // in case the labels needs to be reinitialized // when the code generation is restarted in wide mode if (this.anyExceptionLabelsCount > 0) { this.anyExceptionLabels = NO_EXCEPTION_HANDLER; this.anyExceptionLabelsCount = 0; } int pc = codeStream.position; final int NO_FINALLY = 0; // no finally block final int FINALLY_SUBROUTINE = 1; // finally is generated as a subroutine (using jsr/ret bytecodes) final int FINALLY_DOES_NOT_COMPLETE = 2; // non returning finally is optimized with only one instance of finally block final int FINALLY_MUST_BE_INLINED = 3; // finally block must be inlined since cannot use jsr/ret bytecodes >1.5 int finallyMode; if (subRoutineStartLabel == null) { finallyMode = NO_FINALLY; } else { if (this.isSubRoutineEscaping) { finallyMode = FINALLY_DOES_NOT_COMPLETE; } else if (scope.environment().options.inlineJsrBytecode) { finallyMode = FINALLY_MUST_BE_INLINED; } else { finallyMode = FINALLY_SUBROUTINE; } } boolean requiresNaturalExit = false; // preparing exception labels int maxCatches; ExceptionLabel[] exceptionLabels = new ExceptionLabel[maxCatches = catchArguments == null ? 0 : catchArguments.length]; for (int i = 0; i < maxCatches; i++) { exceptionLabels[i] = new ExceptionLabel(codeStream, catchArguments[i].binding.type); } if (subRoutineStartLabel != null) { subRoutineStartLabel.initialize(codeStream); this.enterAnyExceptionHandler(codeStream); } // generate the try block tryBlock.generateCode(scope, codeStream); boolean tryBlockHasSomeCode = codeStream.position != pc; // flag telling if some bytecodes were issued inside the try block // place end positions of user-defined exception labels if (tryBlockHasSomeCode) { // natural exit may require subroutine invocation (if finally != null) Label naturalExitLabel = new Label(codeStream); if (!tryBlockExit) { int position = codeStream.position; switch(finallyMode) { case FINALLY_SUBROUTINE : case FINALLY_MUST_BE_INLINED : requiresNaturalExit = true; // fall through case NO_FINALLY : codeStream.goto_(naturalExitLabel); break; case FINALLY_DOES_NOT_COMPLETE : codeStream.goto_(subRoutineStartLabel); break; } codeStream.updateLastRecordedEndPC(position); //goto is tagged as part of the try block } for (int i = 0; i < maxCatches; i++) { exceptionLabels[i].placeEnd(); } /* generate sequence of handler, all starting by storing the TOS (exception thrown) into their own catch variables, the one specified in the source that must denote the handled exception. */ if (catchArguments != null) { for (int i = 0; i < maxCatches; i++) { // May loose some local variable initializations : affecting the local variable attributes if (preTryInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); } exceptionLabels[i].place(); codeStream.incrStackSize(1); // optimizing the case where the exception variable is not actually used LocalVariableBinding catchVar; int varPC = codeStream.position; if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) { codeStream.store(catchVar, false); catchVar.recordInitializationStartPC(codeStream.position); codeStream.addVisibleLocalVariable(catchVar); } else { codeStream.pop(); } codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart); // Keep track of the pcs at diverging point for computing the local attribute // since not passing the catchScope, the block generation will exitUserScope(catchScope) catchBlocks[i].generateCode(scope, codeStream); if (!catchExits[i]) { switch(finallyMode) { case FINALLY_SUBROUTINE : case FINALLY_MUST_BE_INLINED : requiresNaturalExit = true; // fall through case NO_FINALLY : codeStream.goto_(naturalExitLabel); break; case FINALLY_DOES_NOT_COMPLETE : codeStream.goto_(subRoutineStartLabel); break; } } } } this.exitAnyExceptionHandler(); // extra handler for trailing natural exit (will be fixed up later on when natural exit is generated below) ExceptionLabel naturalExitExceptionHandler = finallyMode == FINALLY_SUBROUTINE && requiresNaturalExit ? new ExceptionLabel(codeStream, null) : null; // addition of a special handler so as to ensure that any uncaught exception (or exception thrown // inside catch blocks) will run the finally block int finallySequenceStartPC = codeStream.position; if (subRoutineStartLabel != null) { this.placeAllAnyExceptionHandlers(); if (naturalExitExceptionHandler != null) naturalExitExceptionHandler.place(); if (preTryInitStateIndex != -1) { // reset initialization state, as for a normal catch block codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); } codeStream.incrStackSize(1); switch(finallyMode) { case FINALLY_SUBROUTINE : codeStream.store(anyExceptionVariable, false); codeStream.jsr(subRoutineStartLabel); codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); int position = codeStream.position; codeStream.load(anyExceptionVariable); codeStream.athrow(); codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd); subRoutineStartLabel.place(); codeStream.incrStackSize(1); position = codeStream.position; codeStream.store(returnAddressVariable, false); codeStream.recordPositionsFrom(position, finallyBlock.sourceStart); finallyBlock.generateCode(scope, codeStream); position = codeStream.position; codeStream.ret(returnAddressVariable.resolvedPosition); // codeStream.updateLastRecordedEndPC(position); codeStream.recordPositionsFrom( position, finallyBlock.sourceEnd); // the ret bytecode is part of the subroutine break; case FINALLY_MUST_BE_INLINED : codeStream.store(anyExceptionVariable, false); codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); this.finallyBlock.generateCode(currentScope, codeStream); position = codeStream.position; codeStream.load(anyExceptionVariable); codeStream.athrow(); subRoutineStartLabel.place(); codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd); break; case FINALLY_DOES_NOT_COMPLETE : codeStream.pop(); subRoutineStartLabel.place(); codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); finallyBlock.generateCode(scope, codeStream); break; } // will naturally fall into subsequent code after subroutine invocation naturalExitLabel.place(); if (requiresNaturalExit) { switch(finallyMode) { case FINALLY_SUBROUTINE : int position = codeStream.position; // fix up natural exit handler naturalExitExceptionHandler.placeStart(); codeStream.jsr(subRoutineStartLabel); naturalExitExceptionHandler.placeEnd(); codeStream.recordPositionsFrom( position, finallyBlock.sourceEnd); break; case FINALLY_MUST_BE_INLINED : // May loose some local variable initializations : affecting the local variable attributes // needed since any exception handler got inlined subroutine if (preTryInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); } // entire sequence for finally is associated to finally block finallyBlock.generateCode(scope, codeStream); break; case FINALLY_DOES_NOT_COMPLETE : break; } } } else { // no subroutine, simply position end label (natural exit == end) naturalExitLabel.place(); } } else { // try block had no effect, only generate the body of the finally block if any if (subRoutineStartLabel != null) { finallyBlock.generateCode(scope, codeStream); } } // May loose some local variable initializations : affecting the local variable attributes if (mergedInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); } codeStream.recordPositionsFrom(pc, this.sourceStart); } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement#generateSubRoutineInvocation(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.codegen.CodeStream) */ public void generateSubRoutineInvocation( BlockScope currentScope, CodeStream codeStream) { if (this.isSubRoutineEscaping) { codeStream.goto_(this.subRoutineStartLabel); } else { if (currentScope.environment().options.inlineJsrBytecode) { // cannot use jsr bytecode, then simply inline the subroutine this.finallyBlock.generateCode(currentScope, codeStream); } else { // classic subroutine invocation, distinguish case of non-returning subroutine codeStream.jsr(this.subRoutineStartLabel); } } } public StringBuffer printStatement(int indent, StringBuffer output) { printIndent(indent, output).append("try \n"); //$NON-NLS-1$ tryBlock.printStatement(indent + 1, output); //$NON-NLS-1$ //catches if (catchBlocks != null) for (int i = 0; i < catchBlocks.length; i++) { output.append('\n'); printIndent(indent, output).append("catch ("); //$NON-NLS-1$ catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$ catchBlocks[i].printStatement(indent + 1, output); } //finally if (finallyBlock != null) { output.append('\n'); printIndent(indent, output).append("finally\n"); //$NON-NLS-1$ finallyBlock.printStatement(indent + 1, output); } return output; } public void resolve(BlockScope upperScope) { // special scope for secret locals optimization. this.scope = new BlockScope(upperScope); BlockScope tryScope = new BlockScope(scope); BlockScope finallyScope = null; if (finallyBlock != null) { if (finallyBlock.isEmptyBlock()) { if ((finallyBlock.bits & UndocumentedEmptyBlockMASK) != 0) { scope.problemReporter().undocumentedEmptyBlock(finallyBlock.sourceStart, finallyBlock.sourceEnd); } } else { finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope // provision for returning and forcing the finally block to run MethodScope methodScope = scope.methodScope(); // the type does not matter as long as it is not a base type if (!upperScope.environment().options.inlineJsrBytecode) { this.returnAddressVariable = new LocalVariableBinding(SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false); finallyScope.addLocalVariable(returnAddressVariable); this.returnAddressVariable.setConstant(NotAConstant); // not inlinable } this.subRoutineStartLabel = new Label(); this.anyExceptionVariable = new LocalVariableBinding(SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false); finallyScope.addLocalVariable(this.anyExceptionVariable); this.anyExceptionVariable.setConstant(NotAConstant); // not inlinable if (!methodScope.isInsideInitializer()) { MethodBinding methodBinding = ((AbstractMethodDeclaration) methodScope.referenceContext).binding; if (methodBinding != null) { TypeBinding methodReturnType = methodBinding.returnType; if (methodReturnType.id != T_void) { this.secretReturnValue = new LocalVariableBinding( SecretLocalDeclarationName, methodReturnType, AccDefault, false); finallyScope.addLocalVariable(this.secretReturnValue); this.secretReturnValue.setConstant(NotAConstant); // not inlinable } } } finallyBlock.resolveUsing(finallyScope); // force the finally scope to have variable positions shifted after its try scope and catch ones finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length+1]; finallyScope.shiftScopes[0] = tryScope; } } this.tryBlock.resolveUsing(tryScope); // arguments type are checked against JavaLangThrowable in resolveForCatch(..) if (this.catchBlocks != null) { int length = this.catchArguments.length; TypeBinding[] argumentTypes = new TypeBinding[length]; boolean catchHasError = false; for (int i = 0; i < length; i++) { BlockScope catchScope = new BlockScope(scope); if (finallyScope != null){ finallyScope.shiftScopes[i+1] = catchScope; } // side effect on catchScope in resolveForCatch(..) if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null) { catchHasError = true; } catchBlocks[i].resolveUsing(catchScope); } if (catchHasError) { return; } // Verify that the catch clause are ordered in the right way: // more specialized first. this.caughtExceptionTypes = new ReferenceBinding[length]; for (int i = 0; i < length; i++) { caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i]; for (int j = 0; j < i; j++) { if (caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) { scope.problemReporter().wrongSequenceOfExceptionTypesError(this, caughtExceptionTypes[i], i, argumentTypes[j]); } } } } else { caughtExceptionTypes = new ReferenceBinding[0]; } if (finallyScope != null){ // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes. // the shifting is necessary to achieve no overlay in between the finally scope and its // sibling in term of local variable positions. this.scope.addSubscope(finallyScope); } } public void traverse( ASTVisitor visitor, BlockScope blockScope) { if (visitor.visit(this, blockScope)) { tryBlock.traverse(visitor, scope); if (catchArguments != null) { for (int i = 0, max = catchBlocks.length; i < max; i++) { catchArguments[i].traverse(visitor, scope); catchBlocks[i].traverse(visitor, scope); } } if (finallyBlock != null) finallyBlock.traverse(visitor, scope); } visitor.endVisit(this, blockScope); } }