| /******************************************************************************* |
| * 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.*; |
| import org.eclipse.jdt.internal.compiler.flow.*; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.lookup.*; |
| |
| 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.nullInfoLessUnconditionalCopy()) |
| // remove null info to protect point of |
| // exception null info |
| .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.nullInfoLessUnconditionalCopy()) |
| // remove null info to protect point of |
| // exception null info |
| .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); |
| } |
| } |