/******************************************************************************* | |
* 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.wst.jsdt.internal.compiler.ast; | |
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor; | |
import org.eclipse.wst.jsdt.internal.compiler.codegen.*; | |
import org.eclipse.wst.jsdt.internal.compiler.flow.*; | |
import org.eclipse.wst.jsdt.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.wst.jsdt.internal.compiler.ast.SubRoutineStatement#generateSubRoutineInvocation(org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope, org.eclipse.wst.jsdt.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); | |
} | |
} |