/******************************************************************************* * Copyright (c) 2000, 2003 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 net.sourceforge.phpdt.internal.compiler.ast; import net.sourceforge.phpdt.internal.compiler.ASTVisitor; import net.sourceforge.phpdt.internal.compiler.codegen.Label; import net.sourceforge.phpdt.internal.compiler.flow.ExceptionHandlingFlowContext; import net.sourceforge.phpdt.internal.compiler.flow.FinallyFlowContext; import net.sourceforge.phpdt.internal.compiler.flow.FlowContext; import net.sourceforge.phpdt.internal.compiler.flow.FlowInfo; import net.sourceforge.phpdt.internal.compiler.flow.InsideSubRoutineFlowContext; import net.sourceforge.phpdt.internal.compiler.flow.UnconditionalFlowInfo; import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope; import net.sourceforge.phpdt.internal.compiler.lookup.LocalVariableBinding; import net.sourceforge.phpdt.internal.compiler.lookup.MethodBinding; import net.sourceforge.phpdt.internal.compiler.lookup.MethodScope; import net.sourceforge.phpdt.internal.compiler.lookup.ReferenceBinding; import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding; public class TryStatement extends Statement { public Block tryBlock; public Block[] catchBlocks; public Argument[] catchArguments; public Block finallyBlock; BlockScope scope; public boolean subRoutineCannotReturn = true; 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) { 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.isReachable()) { subRoutineCannotReturn = false; } 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.statements == null) { tryInfo = flowInfo; tryBlockExit = false; } else { tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); tryBlockExit = !tryInfo.isReachable(); } // check unreachable catch blocks // handlingContext.complainIfUnusedExceptionHandlers(catchBlocks, 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: 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 cannotReturn() { return subRoutineCannotReturn; } /** * Try statement code generation * */ // public void generateCode(BlockScope currentScope, CodeStream codeStream) // { // // if ((bits & IsReachableMASK) == 0) { // return; // } // if (tryBlock.isEmptyBlock()) { // if (subRoutineStartLabel != null) { // // since not passing the finallyScope, the block generation will // exitUserScope(finallyScope) // finallyBlock.generateCode(scope, codeStream); // } // // May loose some local variable initializations : affecting the local // variable attributes // if (mergedInitStateIndex != -1) { // codeStream.removeNotDefinitelyAssignedVariables( // currentScope, // mergedInitStateIndex); // } // // no local bytecode produced so no need for position remembering // return; // } // int pc = codeStream.position; // Label endLabel = new Label(codeStream); // boolean requiresNaturalJsr = false; // // // preparing exception labels // int maxCatches; // ExceptionLabel[] exceptionLabels = // new ExceptionLabel[maxCatches = // catchArguments == null ? 0 : catchArguments.length]; // for (int i = 0; i < maxCatches; i++) { // boolean preserveCurrentHandler = // (preserveExceptionHandler[i // / ExceptionHandlingFlowContext.BitCacheSize] // & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize))) // != 0; // if (preserveCurrentHandler) { // exceptionLabels[i] = // new ExceptionLabel( // codeStream, // (ReferenceBinding) catchArguments[i].binding.type); // } // } // ExceptionLabel anyExceptionLabel = null; // if (subRoutineStartLabel != null) { // subRoutineStartLabel.codeStream = codeStream; // anyExceptionLabel = new ExceptionLabel(codeStream, null); // } // // generate the try block // tryBlock.generateCode(scope, codeStream); // boolean tryBlockHasSomeCode = codeStream.position != pc; // // flag telling if some bytecodes were issued inside the try block // // // natural exit: only if necessary // boolean nonReturningSubRoutine = // (subRoutineStartLabel != null) && subRoutineCannotReturn; // if ((!tryBlockExit) && tryBlockHasSomeCode) { // int position = codeStream.position; // if (nonReturningSubRoutine) { // codeStream.goto_(subRoutineStartLabel); // } else { // requiresNaturalJsr = true; // codeStream.goto_(endLabel); // } // codeStream.updateLastRecordedEndPC(position); // //goto is tagged as part of the try block // } // // place end positions of user-defined exception labels // if (tryBlockHasSomeCode) { // for (int i = 0; i < maxCatches; i++) { // boolean preserveCurrentHandler = // (preserveExceptionHandler[i / ExceptionHandlingFlowContext.BitCacheSize] // & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize))) // != 0; // if (preserveCurrentHandler) { // 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) { // if (anyExceptionLabel != null) { // anyExceptionLabel.placeEnd(); // } // } else { // for (int i = 0; i < maxCatches; i++) { // boolean preserveCurrentHandler = // (preserveExceptionHandler[i / ExceptionHandlingFlowContext.BitCacheSize] // & (1 << (i % ExceptionHandlingFlowContext.BitCacheSize))) // != 0; // if (preserveCurrentHandler) { // // 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 (i == maxCatches - 1) { // if (anyExceptionLabel != null) { // anyExceptionLabel.placeEnd(); // } // if (subRoutineStartLabel != null) { // if (!catchExits[i] && preserveCurrentHandler) { // requiresNaturalJsr = true; // codeStream.goto_(endLabel); // } // } // } else { // if (!catchExits[i] && preserveCurrentHandler) { // if (nonReturningSubRoutine) { // codeStream.goto_(subRoutineStartLabel); // } else { // requiresNaturalJsr = true; // codeStream.goto_(endLabel); // } // } // } // } // } // // 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) { // // the additional handler is doing: jsr finallyBlock and rethrow // TOS-exception // anyExceptionLabel.place(); // // if (preTryInitStateIndex != -1) { // // reset initialization state, as for a normal catch block // codeStream.removeNotDefinitelyAssignedVariables( // currentScope, // preTryInitStateIndex); // } // // codeStream.incrStackSize(1); // if (nonReturningSubRoutine) { // codeStream.pop(); // // "if subroutine cannot return, no need to jsr/jump to subroutine since // it will be entered in sequence // } else { // codeStream.store(anyExceptionVariable, false); // codeStream.jsr(subRoutineStartLabel); // codeStream.load(anyExceptionVariable); // codeStream.athrow(); // } // } // // end of catch sequence, place label that will correspond to the finally // block beginning, or end of statement // endLabel.place(); // if (subRoutineStartLabel != null) { // if (nonReturningSubRoutine) { // requiresNaturalJsr = false; // } // Label veryEndLabel = new Label(codeStream); // if (requiresNaturalJsr) { // codeStream.jsr(subRoutineStartLabel); // codeStream.goto_(veryEndLabel); // } // subRoutineStartLabel.place(); // if (!nonReturningSubRoutine) { // codeStream.incrStackSize(1); // codeStream.store(returnAddressVariable, false); // } // codeStream.recordPositionsFrom( // finallySequenceStartPC, // finallyBlock.sourceStart); // // entire sequence for finally is associated to finally block // finallyBlock.generateCode(scope, codeStream); // if (!nonReturningSubRoutine) { // int position = codeStream.position; // codeStream.ret(returnAddressVariable.resolvedPosition); // codeStream.updateLastRecordedEndPC(position); // // the ret bytecode is part of the subroutine // } // if (requiresNaturalJsr) { // veryEndLabel.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); // } public void resetStateForCodeGeneration() { if (this.subRoutineStartLabel != null) { this.subRoutineStartLabel.resetStateForCodeGeneration(); } } 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 && finallyBlock.statements != null) { 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 this.returnAddressVariable = new LocalVariableBinding( SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false); finallyScope.addLocalVariable(returnAddressVariable); this.returnAddressVariable.constant = NotAConstant; // not inlinable this.subRoutineStartLabel = new Label(); this.anyExceptionVariable = new LocalVariableBinding( SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false); finallyScope.addLocalVariable(this.anyExceptionVariable); this.anyExceptionVariable.constant = 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.constant = 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]; 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) return; catchBlocks[i].resolveUsing(catchScope); } // 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, i, j); // cannot return - since may still proceed if // unreachable code is ignored (21203) } } } } 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 String toString(int tab) { String s = tabString(tab); // try s = s + "try "; //$NON-NLS-1$ if (tryBlock == Block.None) s = s + "{}"; //$NON-NLS-1$ else s = s + "\n" + tryBlock.toString(tab + 1); //$NON-NLS-1$ // catches if (catchBlocks != null) for (int i = 0; i < catchBlocks.length; i++) s = s + "\n" + tabString(tab) + "catch (" //$NON-NLS-2$ //$NON-NLS-1$ + catchArguments[i].toString(0) + ") " //$NON-NLS-1$ + catchBlocks[i].toString(tab + 1); // finally if (finallyBlock != null) { if (finallyBlock == Block.None) s = s + "\n" + tabString(tab) + "finally {}"; //$NON-NLS-2$ //$NON-NLS-1$ else s = s + "\n" + tabString(tab) + "finally\n" + //$NON-NLS-2$ //$NON-NLS-1$ finallyBlock.toString(tab + 1); } return s; } 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); } }