blob: 9101bce1233164d066f23751b19ae82ad1ece1f6 [file] [log] [blame]
/*******************************************************************************
* 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().discardNullRelatedInitializations())
.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
LocalVariableBinding catchArg = catchArguments[i].binding;
FlowContext catchContext = insideSubContext == null ? flowContext : insideSubContext;
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 (tryBlock.statements == null) {
catchInfo.setReachMode(FlowInfo.UNREACHABLE);
}
catchInfo =
catchBlocks[i].analyseCode(
currentScope,
catchContext,
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.complainOnDeferredChecks(
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);
}
}