/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-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.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; import org.eclipse.jdt.internal.compiler.codegen.ExceptionLabel; import org.eclipse.jdt.internal.compiler.codegen.StackMapFrameCodeStream; import org.eclipse.jdt.internal.compiler.flow.ExceptionHandlingFlowContext; import org.eclipse.jdt.internal.compiler.flow.FinallyFlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowContext; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.jdt.internal.compiler.flow.InsideSubRoutineFlowContext; import org.eclipse.jdt.internal.compiler.flow.NullInfoRegistry; import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; public class TryStatement extends SubRoutineStatement { private final static char[] SECRET_RETURN_ADDRESS_NAME= " returnAddress".toCharArray(); //$NON-NLS-1$ private final static char[] SECRET_ANY_HANDLER_NAME= " anyExceptionHandler".toCharArray(); //$NON-NLS-1$ private final static char[] SECRET_RETURN_VALUE_NAME= " returnValue".toCharArray(); //$NON-NLS-1$ public Block tryBlock; public Block[] catchBlocks; public Argument[] catchArguments; // should rename into subRoutineComplete to be set to false by default public Block finallyBlock; BlockScope scope; public UnconditionalFlowInfo subRoutineInits; ReferenceBinding[] caughtExceptionTypes; boolean[] catchExits; BranchLabel subRoutineStartLabel; public LocalVariableBinding anyExceptionVariable, returnAddressVariable, secretReturnValue; ExceptionLabel[] declaredExceptionLabels; // only set while generating code // for inlining/optimizing JSR instructions private Object[] reusableJSRTargets; private BranchLabel[] reusableJSRSequenceStartLabels; private int[] reusableJSRStateIndexes; private int reusableJSRTargetsCount= 0; private final static int NO_FINALLY= 0; // no finally block private final static int FINALLY_SUBROUTINE= 1; // finally is generated as a subroutine (using jsr/ret bytecodes) private final static int FINALLY_DOES_NOT_COMPLETE= 2; // non returning finally is optimized with only one instance of finally block private final static int FINALLY_INLINE= 3; // finally block must be inlined since cannot use jsr/ret bytecodes >1.5 // for local variables table attributes int mergedInitStateIndex= -1; int preTryInitStateIndex= -1; int naturalExitMergeInitStateIndex= -1; int[] catchExitInitStateIndexes; 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 this.preTryInitStateIndex= currentScope.methodScope().recordInitializationStates(flowInfo); if (this.anyExceptionVariable != null) { this.anyExceptionVariable.useFlag= LocalVariableBinding.USED; } if (this.returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused this.returnAddressVariable.useFlag= LocalVariableBinding.USED; } if (this.subRoutineStartLabel == null) { // no finally block -- this is a simplified copy of the else part // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext= new ExceptionHandlingFlowContext( flowContext, this, this.caughtExceptionTypes, null, this.scope, flowInfo.unconditionalInits()); handlingContext.initsOnFinally= new NullInfoRegistry(flowInfo.unconditionalInits()); // only try blocks initialize that member - may consider creating a // separate class if needed FlowInfo tryInfo; if (this.tryBlock.isEmptyBlock()) { tryInfo= flowInfo; } else { tryInfo= this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); if ((tryInfo.tagBits & FlowInfo.UNREACHABLE) != 0) this.bits|= ASTNode.IsTryBlockExiting; } // check unreachable catch blocks handlingContext.complainIfUnusedExceptionHandlers(this.scope, this); // process the catch blocks - computing the minimal exit depth amongst try/catch if (this.catchArguments != null) { int catchCount; this.catchExits= new boolean[catchCount= this.catchBlocks.length]; this.catchExitInitStateIndexes= new int[catchCount]; 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; if (this.caughtExceptionTypes[i].isUncheckedException(true)) { catchInfo= handlingContext.initsOnFinally.mitigateNullInfoOf( flowInfo.unconditionalCopy(). addPotentialInitializationsFrom( handlingContext.initsOnException( this.caughtExceptionTypes[i])). addPotentialInitializationsFrom(tryInfo). addPotentialInitializationsFrom( handlingContext.initsOnReturn)); } else { catchInfo= flowInfo.unconditionalCopy(). addPotentialInitializationsFrom( handlingContext.initsOnException( this.caughtExceptionTypes[i])) .addPotentialInitializationsFrom(tryInfo.unconditionalCopy()) .addPotentialInitializationsFrom( handlingContext.initsOnReturn. nullInfoLessUnconditionalCopy()); } // catch var is always set LocalVariableBinding catchArg= this.catchArguments[i].binding; catchInfo.markAsDefinitelyAssigned(catchArg); catchInfo.markAsDefinitelyNonNull(catchArg); /* "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]." */ if (this.tryBlock.statements == null) { catchInfo.setReachMode(FlowInfo.UNREACHABLE); } catchInfo= this.catchBlocks[i].analyseCode( currentScope, flowContext, catchInfo); this.catchExitInitStateIndexes[i]= currentScope.methodScope().recordInitializationStates(catchInfo); this.catchExits[i]= (catchInfo.tagBits & FlowInfo.UNREACHABLE) != 0; tryInfo= tryInfo.mergedWith(catchInfo.unconditionalInits()); } } this.mergedInitStateIndex= currentScope.methodScope().recordInitializationStates(tryInfo); // chain up null info registry if (flowContext.initsOnFinally != null) { flowContext.initsOnFinally.add(handlingContext.initsOnFinally); } return tryInfo; } else { InsideSubRoutineFlowContext insideSubContext; FinallyFlowContext finallyContext; UnconditionalFlowInfo subInfo; // analyse finally block first insideSubContext= new InsideSubRoutineFlowContext(flowContext, this); subInfo= this.finallyBlock .analyseCode( currentScope, finallyContext= new FinallyFlowContext(flowContext, this.finallyBlock), flowInfo.nullInfoLessUnconditionalCopy()) .unconditionalInits(); if (subInfo == FlowInfo.DEAD_END) { this.bits|= ASTNode.IsSubRoutineEscaping; this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock); } this.subRoutineInits= subInfo; // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext= new ExceptionHandlingFlowContext( insideSubContext, this, this.caughtExceptionTypes, null, this.scope, flowInfo.unconditionalInits()); handlingContext.initsOnFinally= new NullInfoRegistry(flowInfo.unconditionalInits()); // only try blocks initialize that member - may consider creating a // separate class if needed FlowInfo tryInfo; if (this.tryBlock.isEmptyBlock()) { tryInfo= flowInfo; } else { tryInfo= this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); if ((tryInfo.tagBits & FlowInfo.UNREACHABLE) != 0) this.bits|= ASTNode.IsTryBlockExiting; } // check unreachable catch blocks handlingContext.complainIfUnusedExceptionHandlers(this.scope, this); // process the catch blocks - computing the minimal exit depth amongst try/catch if (this.catchArguments != null) { int catchCount; this.catchExits= new boolean[catchCount= this.catchBlocks.length]; this.catchExitInitStateIndexes= new int[catchCount]; 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; if (this.caughtExceptionTypes[i].isUncheckedException(true)) { catchInfo= handlingContext.initsOnFinally.mitigateNullInfoOf( flowInfo.unconditionalCopy(). addPotentialInitializationsFrom( handlingContext.initsOnException( this.caughtExceptionTypes[i])). addPotentialInitializationsFrom(tryInfo). addPotentialInitializationsFrom( handlingContext.initsOnReturn)); } else { catchInfo= flowInfo.unconditionalCopy() .addPotentialInitializationsFrom( handlingContext.initsOnException( this.caughtExceptionTypes[i])) .addPotentialInitializationsFrom(tryInfo.unconditionalCopy()) .addPotentialInitializationsFrom( handlingContext.initsOnReturn. nullInfoLessUnconditionalCopy()); } // catch var is always set LocalVariableBinding catchArg= this.catchArguments[i].binding; catchInfo.markAsDefinitelyAssigned(catchArg); catchInfo.markAsDefinitelyNonNull(catchArg); /* "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]." */ if (this.tryBlock.statements == null) { catchInfo.setReachMode(FlowInfo.UNREACHABLE); } catchInfo= this.catchBlocks[i].analyseCode( currentScope, insideSubContext, catchInfo); this.catchExitInitStateIndexes[i]= currentScope.methodScope().recordInitializationStates(catchInfo); this.catchExits[i]= (catchInfo.tagBits & FlowInfo.UNREACHABLE) != 0; tryInfo= tryInfo.mergedWith(catchInfo.unconditionalInits()); } } // 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.complainOnDeferredChecks( handlingContext.initsOnFinally.mitigateNullInfoOf( (tryInfo.tagBits & FlowInfo.UNREACHABLE) == 0 ? flowInfo.unconditionalCopy(). addPotentialInitializationsFrom(tryInfo). // lighten the influence of the try block, which may have // exited at any point addPotentialInitializationsFrom(insideSubContext.initsOnReturn) : insideSubContext.initsOnReturn), currentScope); // chain up null info registry if (flowContext.initsOnFinally != null) { flowContext.initsOnFinally.add(handlingContext.initsOnFinally); } this.naturalExitMergeInitStateIndex= currentScope.methodScope().recordInitializationStates(tryInfo); if (subInfo == FlowInfo.DEAD_END) { this.mergedInitStateIndex= currentScope.methodScope().recordInitializationStates(subInfo); return subInfo; } else { FlowInfo mergedInfo= tryInfo.addInitializationsFrom(subInfo); this.mergedInitStateIndex= currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } } } public ExceptionLabel enterAnyExceptionHandler(CodeStream codeStream) { if (this.subRoutineStartLabel == null) return null; return super.enterAnyExceptionHandler(codeStream); } public void enterDeclaredExceptionHandlers(CodeStream codeStream) { for (int i= 0, length= this.declaredExceptionLabels == null ? 0 : this.declaredExceptionLabels.length; i < length; i++) { this.declaredExceptionLabels[i].placeStart(); } } public void exitAnyExceptionHandler() { if (this.subRoutineStartLabel == null) return; super.exitAnyExceptionHandler(); } public void exitDeclaredExceptionHandlers(CodeStream codeStream) { for (int i= 0, length= this.declaredExceptionLabels == null ? 0 : this.declaredExceptionLabels.length; i < length; i++) { this.declaredExceptionLabels[i].placeEnd(); } } private int finallyMode() { if (this.subRoutineStartLabel == null) { return NO_FINALLY; } else if (isSubRoutineEscaping()) { return FINALLY_DOES_NOT_COMPLETE; } else if (this.scope.compilerOptions().inlineJsrBytecode) { return FINALLY_INLINE; } else { return FINALLY_SUBROUTINE; } } /** * 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 ((this.bits & ASTNode.IsReachable) == 0) { return; } boolean isStackMapFrameCodeStream= codeStream instanceof StackMapFrameCodeStream; // in case the labels needs to be reinitialized // when the code generation is restarted in wide mode this.anyExceptionLabel= null; this.reusableJSRTargets= null; this.reusableJSRSequenceStartLabels= null; this.reusableJSRTargetsCount= 0; int pc= codeStream.position; int finallyMode= finallyMode(); boolean requiresNaturalExit= false; // preparing exception labels int maxCatches= this.catchArguments == null ? 0 : this.catchArguments.length; ExceptionLabel[] exceptionLabels; if (maxCatches > 0) { exceptionLabels= new ExceptionLabel[maxCatches]; for (int i= 0; i < maxCatches; i++) { ExceptionLabel exceptionLabel= new ExceptionLabel(codeStream, this.catchArguments[i].binding.type); exceptionLabel.placeStart(); exceptionLabels[i]= exceptionLabel; } } else { exceptionLabels= null; } if (this.subRoutineStartLabel != null) { this.subRoutineStartLabel.initialize(codeStream); enterAnyExceptionHandler(codeStream); } // generate the try block try { this.declaredExceptionLabels= exceptionLabels; this.tryBlock.generateCode(this.scope, codeStream); } finally { this.declaredExceptionLabels= null; } 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) BranchLabel naturalExitLabel= new BranchLabel(codeStream); BranchLabel postCatchesFinallyLabel= null; for (int i= 0; i < maxCatches; i++) { exceptionLabels[i].placeEnd(); } if ((this.bits & ASTNode.IsTryBlockExiting) == 0) { int position= codeStream.position; switch (finallyMode) { case FINALLY_SUBROUTINE: case FINALLY_INLINE: requiresNaturalExit= true; if (this.naturalExitMergeInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } codeStream.goto_(naturalExitLabel); break; case NO_FINALLY: if (this.naturalExitMergeInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } codeStream.goto_(naturalExitLabel); break; case FINALLY_DOES_NOT_COMPLETE: codeStream.goto_(this.subRoutineStartLabel); break; } codeStream.updateLastRecordedEndPC(this.tryBlock.scope, position); //goto is tagged as part of the try block } /* 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. */ exitAnyExceptionHandler(); if (this.catchArguments != null) { postCatchesFinallyLabel= new BranchLabel(codeStream); for (int i= 0; i < maxCatches; i++) { /* * This should not happen. For consistency purpose, if the exception label is never used * we also don't generate the corresponding catch block, otherwise we have some * unreachable bytecodes */ if (exceptionLabels[i].count == 0) continue; enterAnyExceptionHandler(codeStream); // May loose some local variable initializations : affecting the local variable attributes if (this.preTryInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); } codeStream.pushExceptionOnStack(exceptionLabels[i].exceptionType); exceptionLabels[i].place(); // optimizing the case where the exception variable is not actually used LocalVariableBinding catchVar; int varPC= codeStream.position; if ((catchVar= this.catchArguments[i].binding).resolvedPosition != -1) { codeStream.store(catchVar, false); catchVar.recordInitializationStartPC(codeStream.position); codeStream.addVisibleLocalVariable(catchVar); } else { codeStream.pop(); } codeStream.recordPositionsFrom(varPC, this.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) this.catchBlocks[i].generateCode(this.scope, codeStream); exitAnyExceptionHandler(); if (!this.catchExits[i]) { switch (finallyMode) { case FINALLY_INLINE: // inlined finally here can see all merged variables if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).pushStateIndex(this.naturalExitMergeInitStateIndex); } if (this.catchExitInitStateIndexes[i] != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.catchExitInitStateIndexes[i]); codeStream.addDefinitelyAssignedVariables(currentScope, this.catchExitInitStateIndexes[i]); } // entire sequence for finally is associated to finally block this.finallyBlock.generateCode(this.scope, codeStream); codeStream.goto_(postCatchesFinallyLabel); if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).popStateIndex(); } break; case FINALLY_SUBROUTINE: requiresNaturalExit= true; //$FALL-THROUGH$ case NO_FINALLY: if (this.naturalExitMergeInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } codeStream.goto_(naturalExitLabel); break; case FINALLY_DOES_NOT_COMPLETE: codeStream.goto_(this.subRoutineStartLabel); break; } } } } // extra handler for trailing natural exit (will be fixed up later on when natural exit is generated below) ExceptionLabel naturalExitExceptionHandler= requiresNaturalExit && (finallyMode == FINALLY_SUBROUTINE) ? 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 (this.subRoutineStartLabel != null && this.anyExceptionLabel.count != 0) { codeStream.pushExceptionOnStack(this.scope.getJavaLangThrowable()); if (this.preTryInitStateIndex != -1) { // reset initialization state, as for a normal catch block codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); } placeAllAnyExceptionHandler(); if (naturalExitExceptionHandler != null) naturalExitExceptionHandler.place(); switch (finallyMode) { case FINALLY_SUBROUTINE: // any exception handler codeStream.store(this.anyExceptionVariable, false); codeStream.jsr(this.subRoutineStartLabel); codeStream.recordPositionsFrom(finallySequenceStartPC, this.finallyBlock.sourceStart); int position= codeStream.position; codeStream.throwAnyException(this.anyExceptionVariable); codeStream.recordPositionsFrom(position, this.finallyBlock.sourceEnd); // subroutine this.subRoutineStartLabel.place(); codeStream.pushExceptionOnStack(this.scope.getJavaLangThrowable()); position= codeStream.position; codeStream.store(this.returnAddressVariable, false); codeStream.recordPositionsFrom(position, this.finallyBlock.sourceStart); this.finallyBlock.generateCode(this.scope, codeStream); position= codeStream.position; codeStream.ret(this.returnAddressVariable.resolvedPosition); codeStream.recordPositionsFrom( position, this.finallyBlock.sourceEnd); // the ret bytecode is part of the subroutine break; case FINALLY_INLINE: // any exception handler codeStream.store(this.anyExceptionVariable, false); codeStream.addVariable(this.anyExceptionVariable); codeStream.recordPositionsFrom(finallySequenceStartPC, this.finallyBlock.sourceStart); // subroutine this.finallyBlock.generateCode(currentScope, codeStream); position= codeStream.position; codeStream.throwAnyException(this.anyExceptionVariable); codeStream.removeVariable(this.anyExceptionVariable); if (this.preTryInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); } this.subRoutineStartLabel.place(); codeStream.recordPositionsFrom(position, this.finallyBlock.sourceEnd); break; case FINALLY_DOES_NOT_COMPLETE: // any exception handler codeStream.pop(); this.subRoutineStartLabel.place(); codeStream.recordPositionsFrom(finallySequenceStartPC, this.finallyBlock.sourceStart); // subroutine this.finallyBlock.generateCode(this.scope, codeStream); break; } // will naturally fall into subsequent code after subroutine invocation if (requiresNaturalExit) { switch (finallyMode) { case FINALLY_SUBROUTINE: naturalExitLabel.place(); int position= codeStream.position; naturalExitExceptionHandler.placeStart(); codeStream.jsr(this.subRoutineStartLabel); naturalExitExceptionHandler.placeEnd(); codeStream.recordPositionsFrom( position, this.finallyBlock.sourceEnd); break; case FINALLY_INLINE: // inlined finally here can see all merged variables if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).pushStateIndex(this.naturalExitMergeInitStateIndex); } if (this.naturalExitMergeInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } naturalExitLabel.place(); // entire sequence for finally is associated to finally block this.finallyBlock.generateCode(this.scope, codeStream); if (postCatchesFinallyLabel != null) { position= codeStream.position; // entire sequence for finally is associated to finally block codeStream.goto_(postCatchesFinallyLabel); codeStream.recordPositionsFrom( position, this.finallyBlock.sourceEnd); } if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).popStateIndex(); } break; case FINALLY_DOES_NOT_COMPLETE: break; default: naturalExitLabel.place(); break; } } if (postCatchesFinallyLabel != null) { postCatchesFinallyLabel.place(); } } 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 (this.subRoutineStartLabel != null) { this.finallyBlock.generateCode(this.scope, codeStream); } } // May loose some local variable initializations : affecting the local variable attributes if (this.mergedInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); } codeStream.recordPositionsFrom(pc, this.sourceStart); } /** * @see SubRoutineStatement#generateSubRoutineInvocation(BlockScope, CodeStream, Object, int, * LocalVariableBinding) */ public boolean generateSubRoutineInvocation(BlockScope currentScope, CodeStream codeStream, Object targetLocation, int stateIndex, LocalVariableBinding secretLocal) { boolean isStackMapFrameCodeStream= codeStream instanceof StackMapFrameCodeStream; int finallyMode= finallyMode(); switch (finallyMode) { case FINALLY_DOES_NOT_COMPLETE: codeStream.goto_(this.subRoutineStartLabel); return true; case NO_FINALLY: exitDeclaredExceptionHandlers(codeStream); return false; } // optimize subroutine invocation sequences, using the targetLocation (if any) if (targetLocation != null) { boolean reuseTargetLocation= true; if (this.reusableJSRTargetsCount > 0) { nextReusableTarget: for (int i= 0, count= this.reusableJSRTargetsCount; i < count; i++) { Object reusableJSRTarget= this.reusableJSRTargets[i]; differentTarget: { if (targetLocation == reusableJSRTarget) break differentTarget; if (targetLocation instanceof Constant && reusableJSRTarget instanceof Constant && ((Constant)targetLocation).hasSameValue((Constant)reusableJSRTarget)) { break differentTarget; } // cannot reuse current target continue nextReusableTarget; } // current target has been used in the past, simply branch to its label if ((this.reusableJSRStateIndexes[i] != stateIndex) && finallyMode == FINALLY_INLINE) { reuseTargetLocation= false; break nextReusableTarget; } else { codeStream.goto_(this.reusableJSRSequenceStartLabels[i]); return true; } } } else { this.reusableJSRTargets= new Object[3]; this.reusableJSRSequenceStartLabels= new BranchLabel[3]; this.reusableJSRStateIndexes= new int[3]; } if (reuseTargetLocation) { if (this.reusableJSRTargetsCount == this.reusableJSRTargets.length) { System.arraycopy(this.reusableJSRTargets, 0, this.reusableJSRTargets= new Object[2 * this.reusableJSRTargetsCount], 0, this.reusableJSRTargetsCount); System.arraycopy(this.reusableJSRSequenceStartLabels, 0, this.reusableJSRSequenceStartLabels= new BranchLabel[2 * this.reusableJSRTargetsCount], 0, this.reusableJSRTargetsCount); System.arraycopy(this.reusableJSRStateIndexes, 0, this.reusableJSRStateIndexes= new int[2 * this.reusableJSRTargetsCount], 0, this.reusableJSRTargetsCount); } this.reusableJSRTargets[this.reusableJSRTargetsCount]= targetLocation; BranchLabel reusableJSRSequenceStartLabel= new BranchLabel(codeStream); reusableJSRSequenceStartLabel.place(); this.reusableJSRStateIndexes[this.reusableJSRTargetsCount]= stateIndex; this.reusableJSRSequenceStartLabels[this.reusableJSRTargetsCount++]= reusableJSRSequenceStartLabel; } } if (finallyMode == FINALLY_INLINE) { if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).pushStateIndex(stateIndex); if (this.naturalExitMergeInitStateIndex != -1 || stateIndex != -1) { // reset initialization state, as for a normal catch block codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } } else { if (this.naturalExitMergeInitStateIndex != -1) { // reset initialization state, as for a normal catch block codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.naturalExitMergeInitStateIndex); } } if (secretLocal != null) { codeStream.addVariable(secretLocal); } // cannot use jsr bytecode, then simply inline the subroutine // inside try block, ensure to deactivate all catch block exception handlers while inlining finally block exitAnyExceptionHandler(); exitDeclaredExceptionHandlers(codeStream); this.finallyBlock.generateCode(currentScope, codeStream); if (isStackMapFrameCodeStream) { ((StackMapFrameCodeStream)codeStream).popStateIndex(); } } else { // classic subroutine invocation, distinguish case of non-returning subroutine codeStream.jsr(this.subRoutineStartLabel); exitAnyExceptionHandler(); exitDeclaredExceptionHandlers(codeStream); } return false; } public boolean isSubRoutineEscaping() { return (this.bits & ASTNode.IsSubRoutineEscaping) != 0; } public StringBuffer printStatement(int indent, StringBuffer output) { printIndent(indent, output).append("try \n"); //$NON-NLS-1$ this.tryBlock.printStatement(indent + 1, output); //catches if (this.catchBlocks != null) for (int i= 0; i < this.catchBlocks.length; i++) { output.append('\n'); printIndent(indent, output).append("catch ("); //$NON-NLS-1$ this.catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$ this.catchBlocks[i].printStatement(indent + 1, output); } //finally if (this.finallyBlock != null) { output.append('\n'); printIndent(indent, output).append("finally\n"); //$NON-NLS-1$ this.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(this.scope); BlockScope finallyScope= null; if (this.finallyBlock != null) { if (this.finallyBlock.isEmptyBlock()) { if ((this.finallyBlock.bits & ASTNode.UndocumentedEmptyBlock) != 0) { this.scope.problemReporter().undocumentedEmptyBlock(this.finallyBlock.sourceStart, this.finallyBlock.sourceEnd); } } else { finallyScope= new BlockScope(this.scope, false); // don't add it yet to parent scope // provision for returning and forcing the finally block to run MethodScope methodScope= this.scope.methodScope(); // the type does not matter as long as it is not a base type if (!upperScope.compilerOptions().inlineJsrBytecode) { this.returnAddressVariable= new LocalVariableBinding(TryStatement.SECRET_RETURN_ADDRESS_NAME, upperScope.getJavaLangObject(), ClassFileConstants.AccDefault, false); finallyScope.addLocalVariable(this.returnAddressVariable); this.returnAddressVariable.setConstant(Constant.NotAConstant); // not inlinable } this.subRoutineStartLabel= new BranchLabel(); this.anyExceptionVariable= new LocalVariableBinding(TryStatement.SECRET_ANY_HANDLER_NAME, this.scope.getJavaLangThrowable(), ClassFileConstants.AccDefault, false); finallyScope.addLocalVariable(this.anyExceptionVariable); this.anyExceptionVariable.setConstant(Constant.NotAConstant); // not inlinable if (!methodScope.isInsideInitializer()) { MethodBinding methodBinding= ((AbstractMethodDeclaration)methodScope.referenceContext).binding; if (methodBinding != null) { TypeBinding methodReturnType= methodBinding.returnType; if (methodReturnType.id != TypeIds.T_void) { this.secretReturnValue= new LocalVariableBinding( TryStatement.SECRET_RETURN_VALUE_NAME, methodReturnType, ClassFileConstants.AccDefault, false); finallyScope.addLocalVariable(this.secretReturnValue); this.secretReturnValue.setConstant(Constant.NotAConstant); // not inlinable } } } this.finallyBlock.resolveUsing(finallyScope); // force the finally scope to have variable positions shifted after its try scope and catch ones finallyScope.shiftScopes= new BlockScope[this.catchArguments == null ? 1 : this.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(this.scope); if (finallyScope != null) { finallyScope.shiftScopes[i + 1]= catchScope; } // side effect on catchScope in resolveForCatch(..) if ((argumentTypes[i]= this.catchArguments[i].resolveForCatch(catchScope)) == null) { catchHasError= true; } this.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++) { this.caughtExceptionTypes[i]= (ReferenceBinding)argumentTypes[i]; for (int j= 0; j < i; j++) { if (this.caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) { this.scope.problemReporter().wrongSequenceOfExceptionTypesError(this, this.caughtExceptionTypes[i], i, argumentTypes[j]); } } } } else { this.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)) { this.tryBlock.traverse(visitor, this.scope); if (this.catchArguments != null) { for (int i= 0, max= this.catchBlocks.length; i < max; i++) { this.catchArguments[i].traverse(visitor, this.scope); this.catchBlocks[i].traverse(visitor, this.scope); } } if (this.finallyBlock != null) this.finallyBlock.traverse(visitor, this.scope); } visitor.endVisit(this, blockScope); } }