/*******************************************************************************
* Copyright (c) 2000, 2011 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
* Stephan Herrmann - Contribution for bug 332637 - Dead Code detection removing code that isn't dead
*******************************************************************************/
package org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast;
import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ASTVisitor;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ClassFileConstants;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.codegen.BranchLabel;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.codegen.ConstantPool;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.ExceptionHandlingFlowContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FinallyFlowContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.InsideSubRoutineFlowContext;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.NullInfoRegistry;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.UnconditionalFlowInfo;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.Constant;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeIds;
public class TryStatement extends SubRoutineStatement {
static final char[] SECRET_RETURN_ADDRESS_NAME = " returnAddress".toCharArray(); //$NON-NLS-1$
static final char[] SECRET_ANY_HANDLER_NAME = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$
static final char[] SECRET_PRIMARY_EXCEPTION_VARIABLE_NAME = " primaryException".toCharArray(); //$NON-NLS-1$
static final char[] SECRET_CAUGHT_THROWABLE_VARIABLE_NAME = " caughtThrowable".toCharArray(); //$NON-NLS-1$;
static final char[] SECRET_RETURN_VALUE_NAME = " returnValue".toCharArray(); //$NON-NLS-1$
private static LocalDeclaration[] NO_RESOURCES = new LocalDeclaration[0];
public LocalDeclaration[] resources = NO_RESOURCES;
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;
private static final int NO_FINALLY = 0; // no finally block
private static final int FINALLY_SUBROUTINE = 1; // finally is generated as a subroutine (using jsr/ret bytecodes)
private static final int FINALLY_DOES_NOT_COMPLETE = 2; // non returning finally is optimized with only one instance of finally block
private static final 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[] postResourcesInitStateIndexes;
int naturalExitMergeInitStateIndex = -1;
int[] catchExitInitStateIndexes;
private LocalVariableBinding primaryExceptionVariable;
private LocalVariableBinding caughtThrowableVariable;
private int[] caughtExceptionsCatchBlocks;
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.primaryExceptionVariable != null) {
this.primaryExceptionVariable.useFlag = LocalVariableBinding.USED;
}
if (this.caughtThrowableVariable != null) {
this.caughtThrowableVariable.useFlag = LocalVariableBinding.USED;
}
if (this.returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused
this.returnAddressVariable.useFlag = LocalVariableBinding.USED;
}
int resourcesLength = this.resources.length;
if (resourcesLength > 0) {
this.postResourcesInitStateIndexes = new int[resourcesLength];
}
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,
this.caughtExceptionsCatchBlocks, this.catchArguments, 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
for (int i = 0; i < resourcesLength; i++) {
flowInfo = this.resources[i].analyseCode(currentScope, handlingContext, flowInfo.copy());
this.postResourcesInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(flowInfo);
this.resources[i].binding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways.
TypeBinding type = this.resources[i].binding.type;
if (type != null && type.isValidBinding()) {
ReferenceBinding binding = (ReferenceBinding)type;
MethodBinding closeMethod =
binding.getExactMethod(ConstantPool.Close, new TypeBinding[0],
this.scope.compilationUnitScope()); // scope needs to be tighter
if (closeMethod != null && closeMethod.returnType.id == TypeIds.T_void) {
ReferenceBinding[] thrownExceptions = closeMethod.thrownExceptions;
for (int j = 0, length = thrownExceptions.length; j < length; j++) {
handlingContext.checkExceptionHandlers(thrownExceptions[j], this.resources[i], flowInfo,
currentScope, true);
}
}
}
}
FlowInfo tryInfo;
if (this.tryBlock.isEmptyBlock()) {
tryInfo = flowInfo;
} else {
tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
if ((tryInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 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 (isUncheckedCatchBlock(i)) {
catchInfo =
handlingContext.initsOnFinally.mitigateNullInfoOf(flowInfo.unconditionalCopy()
.addPotentialInitializationsFrom(
handlingContext.initsOnException(i))
.addPotentialInitializationsFrom(tryInfo)
.addPotentialInitializationsFrom(
handlingContext.initsOnReturn));
} else {
FlowInfo initsOnException = handlingContext.initsOnException(i);
catchInfo =
flowInfo.nullInfoLessUnconditionalCopy().addPotentialInitializationsFrom(initsOnException)
.addNullInfoFrom(initsOnException)
// null info only from here, this is the only way to enter the catch block
.addPotentialInitializationsFrom(tryInfo.nullInfoLessUnconditionalCopy())
.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 &&
this.resources == NO_RESOURCES) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=350579
catchInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
catchInfo = this.catchBlocks[i].analyseCode(currentScope, flowContext, catchInfo);
this.catchExitInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(catchInfo);
this.catchExits[i] = (catchInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 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,
this.caughtExceptionsCatchBlocks, this.catchArguments, 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
for (int i = 0; i < resourcesLength; i++) {
flowInfo = this.resources[i].analyseCode(currentScope, handlingContext, flowInfo.copy());
this.postResourcesInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(flowInfo);
this.resources[i].binding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways.
TypeBinding type = this.resources[i].binding.type;
if (type != null && type.isValidBinding()) {
ReferenceBinding binding = (ReferenceBinding)type;
MethodBinding closeMethod =
binding.getExactMethod(ConstantPool.Close, new TypeBinding[0],
this.scope.compilationUnitScope()); // scope needs to be tighter
if (closeMethod != null && closeMethod.returnType.id == TypeIds.T_void) {
ReferenceBinding[] thrownExceptions = closeMethod.thrownExceptions;
for (int j = 0, length = thrownExceptions.length; j < length; j++) {
handlingContext.checkExceptionHandlers(thrownExceptions[j], this.resources[j], flowInfo,
currentScope);
}
}
}
}
FlowInfo tryInfo;
if (this.tryBlock.isEmptyBlock()) {
tryInfo = flowInfo;
} else {
tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
if ((tryInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 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 (isUncheckedCatchBlock(i)) {
catchInfo =
handlingContext.initsOnFinally.mitigateNullInfoOf(flowInfo.unconditionalCopy()
.addPotentialInitializationsFrom(
handlingContext.initsOnException(i))
.addPotentialInitializationsFrom(tryInfo)
.addPotentialInitializationsFrom(
handlingContext.initsOnReturn));
} else {
FlowInfo initsOnException = handlingContext.initsOnException(i);
catchInfo =
flowInfo.nullInfoLessUnconditionalCopy().addPotentialInitializationsFrom(initsOnException)
.addNullInfoFrom(initsOnException)
// null info only from here, this is the only way to enter the catch block
.addPotentialInitializationsFrom(tryInfo.nullInfoLessUnconditionalCopy())
.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 &&
this.resources == NO_RESOURCES) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=350579
catchInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
}
catchInfo = this.catchBlocks[i].analyseCode(currentScope, insideSubContext, catchInfo);
this.catchExitInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(catchInfo);
this.catchExits[i] = (catchInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 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;
}
}
}
// Return true if the catch block corresponds to an unchecked exception making allowance for multi-catch blocks.
private boolean isUncheckedCatchBlock(int catchBlock) {
if (this.caughtExceptionsCatchBlocks == null) {
return this.caughtExceptionTypes[catchBlock].isUncheckedException(true);
}
for (int i = 0, length = this.caughtExceptionsCatchBlocks.length; i < length; i++) {
if (this.caughtExceptionsCatchBlocks[i] == catchBlock) {
if (this.caughtExceptionTypes[i].isUncheckedException(true)) {
return true;
}
}
}
return false;
}
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) {
if ((this.bits & ASTNode.IsReachable) == 0) {
return;
}
finallyMode();
// generate the try block
int resourceCount = this.resources.length;
if (resourceCount > 0) {
// Please see https://bugs.eclipse.org/bugs/show_bug.cgi?id=338402#c16
for (int i = 0; i <= resourceCount; i++) {
// put null for the exception type to treat them as any exception handlers (equivalent to a try/finally)
if (i < resourceCount) {
this.resources[i].generateCode(this.scope); // Initialize resources ...
}
}
}
this.tryBlock.generateCode(this.scope);
// flag telling if some bytecodes were issued inside the try block
}
/** @see SubRoutineStatement#generateSubRoutineInvocation(BlockScope, CodeStream, Object, int, LocalVariableBinding) */
public boolean generateSubRoutineInvocation(BlockScope currentScope, Object targetLocation, int stateIndex,
LocalVariableBinding secretLocal) {
return false;
}
public boolean isSubRoutineEscaping() {
return (this.bits & ASTNode.IsSubRoutineEscaping) != 0;
}
public StringBuffer printStatement(int indent, StringBuffer output) {
int length = this.resources.length;
printIndent(indent, output).append("try" + (length == 0 ? "\n" : " (")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
for (int i = 0; i < length; i++) {
this.resources[i].printAsExpression(0, output);
if (i != length - 1) {
output.append(";\n"); //$NON-NLS-1$
printIndent(indent + 2, output);
}
}
if (length > 0) {
output.append(")\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(")\n"); //$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 finallyScope = null;
BlockScope resourceManagementScope = null; // Single scope to hold all resources and additional secret variables.
int resourceCount = this.resources.length;
if (resourceCount > 0) {
resourceManagementScope = new BlockScope(this.scope);
this.primaryExceptionVariable =
new LocalVariableBinding(TryStatement.SECRET_PRIMARY_EXCEPTION_VARIABLE_NAME,
this.scope.getJavaLangThrowable(), ClassFileConstants.AccDefault, false);
resourceManagementScope.addLocalVariable(this.primaryExceptionVariable);
this.primaryExceptionVariable.setConstant(Constant.NotAConstant); // not inlinable
this.caughtThrowableVariable =
new LocalVariableBinding(TryStatement.SECRET_CAUGHT_THROWABLE_VARIABLE_NAME,
this.scope.getJavaLangThrowable(), ClassFileConstants.AccDefault, false);
resourceManagementScope.addLocalVariable(this.caughtThrowableVariable);
this.caughtThrowableVariable.setConstant(Constant.NotAConstant); // not inlinable
}
for (int i = 0; i < resourceCount; i++) {
this.resources[i].resolve(resourceManagementScope);
LocalVariableBinding localVariableBinding = this.resources[i].binding;
if (localVariableBinding != null && localVariableBinding.isValidBinding()) {
localVariableBinding.modifiers |= ClassFileConstants.AccFinal;
localVariableBinding.tagBits |= TagBits.IsResource;
TypeBinding resourceType = localVariableBinding.type;
if (resourceType instanceof ReferenceBinding) {
if (resourceType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaLangAutoCloseable, false /*AutoCloseable is not a class*/) ==
null
&& resourceType.isValidBinding()) {
upperScope.problemReporter()
.resourceHasToImplementAutoCloseable(resourceType, this.resources[i].type);
localVariableBinding.type =
new ProblemReferenceBinding(CharOperation.splitOn('.', resourceType.shortReadableName()), null,
ProblemReasons.InvalidTypeForAutoManagedResource);
}
} else if (resourceType !=
null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=349862, avoid secondary error in problematic null case
upperScope.problemReporter().resourceHasToImplementAutoCloseable(resourceType, this.resources[i].type);
localVariableBinding.type =
new ProblemReferenceBinding(CharOperation.splitOn('.', resourceType.shortReadableName()), null,
ProblemReasons.InvalidTypeForAutoManagedResource);
}
}
}
BlockScope tryScope = new BlockScope(resourceManagementScope != null ? resourceManagementScope : this.scope);
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
int shiftScopesLength = this.catchArguments == null ? 1 : this.catchArguments.length + 1;
finallyScope.shiftScopes = new BlockScope[shiftScopesLength];
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 containsUnionTypes = false;
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(..)
Argument catchArgument = this.catchArguments[i];
containsUnionTypes |= (catchArgument.type.bits & ASTNode.IsUnionType) != 0;
if ((argumentTypes[i] = catchArgument.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.
verifyDuplicationAndOrder(length, argumentTypes, containsUnionTypes);
} 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)) {
LocalDeclaration[] localDeclarations = this.resources;
for (int i = 0, max = localDeclarations.length; i < max; i++) {
localDeclarations[i].traverse(visitor, this.scope);
}
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);
}
protected void verifyDuplicationAndOrder(int length, TypeBinding[] argumentTypes, boolean containsUnionTypes) {
// Verify that the catch clause are ordered in the right way:
// more specialized first.
if (containsUnionTypes) {
int totalCount = 0;
ReferenceBinding[][] allExceptionTypes = new ReferenceBinding[length][];
for (int i = 0; i < length; i++) {
ReferenceBinding currentExceptionType = (ReferenceBinding)argumentTypes[i];
TypeReference catchArgumentType = this.catchArguments[i].type;
if ((catchArgumentType.bits & ASTNode.IsUnionType) != 0) {
TypeReference[] typeReferences = ((UnionTypeReference)catchArgumentType).typeReferences;
int typeReferencesLength = typeReferences.length;
ReferenceBinding[] unionExceptionTypes = new ReferenceBinding[typeReferencesLength];
for (int j = 0; j < typeReferencesLength; j++) {
unionExceptionTypes[j] = (ReferenceBinding)typeReferences[j].resolvedType;
}
totalCount += typeReferencesLength;
allExceptionTypes[i] = unionExceptionTypes;
} else {
allExceptionTypes[i] = new ReferenceBinding[]{currentExceptionType};
totalCount++;
}
}
this.caughtExceptionTypes = new ReferenceBinding[totalCount];
this.caughtExceptionsCatchBlocks = new int[totalCount];
for (int i = 0, l = 0; i < length; i++) {
ReferenceBinding[] currentExceptions = allExceptionTypes[i];
loop:
for (int j = 0, max = currentExceptions.length; j < max; j++) {
ReferenceBinding exception = currentExceptions[j];
this.caughtExceptionTypes[l] = exception;
this.caughtExceptionsCatchBlocks[l++] = i;
// now iterate over all previous exceptions
for (int k = 0; k < i; k++) {
ReferenceBinding[] exceptions = allExceptionTypes[k];
for (int n = 0, max2 = exceptions.length; n < max2; n++) {
ReferenceBinding currentException = exceptions[n];
if (exception.isCompatibleWith(currentException)) {
TypeReference catchArgumentType = this.catchArguments[i].type;
if ((catchArgumentType.bits & ASTNode.IsUnionType) != 0) {
catchArgumentType = ((UnionTypeReference)catchArgumentType).typeReferences[j];
}
this.scope.problemReporter().wrongSequenceOfExceptionTypesError(catchArgumentType, exception,
currentException);
break loop;
}
}
}
}
}
} else {
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.catchArguments[i].type,
this.caughtExceptionTypes[i], argumentTypes[j]);
}
}
}
}
}
}