Copyright (c) 2000, 2018 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Stephan Herrmann - Contributions for bug 332637 - Dead Code detection removing code that isn't dead bug 358827 - [1.7] exception analysis for t-w-r spoils null analysis bug 349326 - [1.7] new warning for missing try-with-resources bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points bug 358903 - Filter practically unimportant resource leak warnings bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" bug 388996 - [compiler][resource] Incorrect 'potential resource leak' bug 401088 - [compiler][null] Wrong warning "Redundant null check" inside nested try statement bug 401092 - [compiler][null] Wrong warning "Redundant null check" in outer catch of nested try bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check bug 384380 - False positive on a ?? Potential null pointer access ?? after a continue Bug 415790 - [compiler][resource]Incorrect potential resource leak warning in for loop with close in try/catch Bug 371614 - [compiler][resource] Wrong "resource leak" problem on return/throw inside while loop Bug 444964 - [1.7+][resource] False resource leak warning (try-with-resources for ByteArrayOutputStream - return inside for loop) Jesper Steen Moller - Contributions for bug 404146 - [1.7][compiler] nested try-catch-finally-blocks leads to unrunnable Java byte code Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for Bug 383624 - [1.8][compiler] Revive code generation support for type annotations (from Olivier's work)
/******************************************************************************* * Copyright (c) 2000, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contributions for * bug 332637 - Dead Code detection removing code that isn't dead * bug 358827 - [1.7] exception analysis for t-w-r spoils null analysis * bug 349326 - [1.7] new warning for missing try-with-resources * bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points * bug 358903 - Filter practically unimportant resource leak warnings * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" * bug 388996 - [compiler][resource] Incorrect 'potential resource leak' * bug 401088 - [compiler][null] Wrong warning "Redundant null check" inside nested try statement * bug 401092 - [compiler][null] Wrong warning "Redundant null check" in outer catch of nested try * bug 402993 - [null] Follow up of bug 401088: Missing warning about redundant null check * bug 384380 - False positive on a ?? Potential null pointer access ?? after a continue * Bug 415790 - [compiler][resource]Incorrect potential resource leak warning in for loop with close in try/catch * Bug 371614 - [compiler][resource] Wrong "resource leak" problem on return/throw inside while loop * Bug 444964 - [1.7+][resource] False resource leak warning (try-with-resources for ByteArrayOutputStream - return inside for loop) * Jesper Steen Moller - Contributions for * bug 404146 - [1.7][compiler] nested try-catch-finally-blocks leads to unrunnable Java byte code * Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for * Bug 383624 - [1.8][compiler] Revive code generation support for type annotations (from Olivier's work) * *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.core.compiler.CharOperation; 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.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.*; 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$ public Statement[] resources = new Statement[0]; public Block tryBlock; public Block[] catchBlocks; public Argument[] catchArguments; 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 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 postTryInitStateIndex = -1; int[] postResourcesInitStateIndexes; int naturalExitMergeInitStateIndex = -1; int[] catchExitInitStateIndexes; private LocalVariableBinding primaryExceptionVariable; private LocalVariableBinding caughtThrowableVariable; private ExceptionLabel[] resourceExceptionLabels; private int[] caughtExceptionsCatchBlocks; @Override 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 if (flowContext instanceof FinallyFlowContext) { // if this TryStatement sits inside another TryStatement, establish the wiring so that // FlowContext.markFinallyNullStatus can report into initsOnFinally of the outer try context: FinallyFlowContext finallyContext = (FinallyFlowContext) flowContext; finallyContext.outerTryContext = finallyContext.tryContext; } // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext( flowContext, this, this.caughtExceptionTypes, this.caughtExceptionsCatchBlocks, null, this.scope, flowInfo); handlingContext.conditionalLevel = 0; // start collection initsOnFinally // only try blocks initialize that member - may consider creating a // separate class if needed FlowInfo tryInfo = flowInfo.copy(); for (int i = 0; i < resourcesLength; i++) { final Statement resource = this.resources[i]; tryInfo = resource.analyseCode(currentScope, handlingContext, tryInfo); this.postResourcesInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(tryInfo); TypeBinding resolvedType = null; LocalVariableBinding localVariableBinding = null; if (resource instanceof LocalDeclaration) { localVariableBinding = ((LocalDeclaration) resource).binding; resolvedType = localVariableBinding.type; } else { //expression if (resource instanceof NameReference && ((NameReference) resource).binding instanceof LocalVariableBinding) { localVariableBinding = (LocalVariableBinding) ((NameReference) resource).binding; } resolvedType = ((Expression) resource).resolvedType; } if (localVariableBinding != null) { localVariableBinding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. if (localVariableBinding.closeTracker != null) { // this was false alarm, we don't need to track the resource localVariableBinding.closeTracker.withdraw(); localVariableBinding.closeTracker = null; } } MethodBinding closeMethod = findCloseMethod(resource, resolvedType); if (closeMethod != null && closeMethod.isValidBinding() && 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], tryInfo, currentScope, true); } } } if (!this.tryBlock.isEmptyBlock()) { tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, tryInfo); if ((tryInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) this.bits |= ASTNode.IsTryBlockExiting; } if (resourcesLength > 0) { this.postTryInitStateIndex = currentScope.methodScope().recordInitializationStates(tryInfo); // the resources are not in scope after the try block, so remove their assignment info // to avoid polluting the state indices. However, do this after the postTryInitStateIndex is calculated since // it is used to add or remove assigned resources during code gen for (int i = 0; i < resourcesLength; i++) { if (this.resources[i] instanceof LocalDeclaration) tryInfo.resetAssignmentInfo(((LocalDeclaration) this.resources[i]).binding); } } // 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 = prepareCatchInfo(flowInfo, handlingContext, tryInfo, i); flowContext.conditionalLevel++; catchInfo = this.catchBlocks[i].analyseCode( currentScope, flowContext, catchInfo); flowContext.conditionalLevel--; 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 flowContext.mergeFinallyNullInfo(handlingContext.initsOnFinally); return tryInfo; } else { InsideSubRoutineFlowContext insideSubContext; FinallyFlowContext finallyContext; UnconditionalFlowInfo subInfo; // analyse finally block first insideSubContext = new InsideSubRoutineFlowContext(flowContext, this); if (flowContext instanceof FinallyFlowContext) { // if this TryStatement sits inside another TryStatement, establish the wiring so that // FlowContext.markFinallyNullStatus can report into initsOnFinally of the outer try context: insideSubContext.outerTryContext = ((FinallyFlowContext)flowContext).tryContext; } // process the try block in a context handling the local exceptions. // (advance instantiation so we can wire this into the FinallyFlowContext) ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext( insideSubContext, this, this.caughtExceptionTypes, this.caughtExceptionsCatchBlocks, null, this.scope, flowInfo); insideSubContext.initsOnFinally = handlingContext.initsOnFinally; subInfo = this.finallyBlock .analyseCode( currentScope, finallyContext = new FinallyFlowContext(flowContext, this.finallyBlock, handlingContext), flowInfo.nullInfoLessUnconditionalCopy()) .unconditionalInits(); handlingContext.conditionalLevel = 0; // start collection initsOnFinally only after analysing the finally block if (subInfo == FlowInfo.DEAD_END) { this.bits |= ASTNode.IsSubRoutineEscaping; this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock); } else { // for resource analysis we need the finallyInfo in these nested scopes: FlowInfo finallyInfo = subInfo.copy(); this.tryBlock.scope.finallyInfo = finallyInfo; if (this.catchBlocks != null) { for (int i = 0; i < this.catchBlocks.length; i++) this.catchBlocks[i].scope.finallyInfo = finallyInfo; } } this.subRoutineInits = subInfo; // only try blocks initialize that member - may consider creating a // separate class if needed FlowInfo tryInfo = flowInfo.copy(); for (int i = 0; i < resourcesLength; i++) { final Statement resource = this.resources[i]; tryInfo = resource.analyseCode(currentScope, handlingContext, tryInfo); this.postResourcesInitStateIndexes[i] = currentScope.methodScope().recordInitializationStates(tryInfo); TypeBinding resolvedType = null; LocalVariableBinding localVariableBinding = null; if (resource instanceof LocalDeclaration) { localVariableBinding = ((LocalDeclaration) this.resources[i]).binding; resolvedType = localVariableBinding.type; } else { // Expression if (resource instanceof NameReference && ((NameReference) resource).binding instanceof LocalVariableBinding) { localVariableBinding = (LocalVariableBinding)((NameReference) resource).binding; } resolvedType = ((Expression) resource).resolvedType; } if (localVariableBinding != null) { localVariableBinding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. if (localVariableBinding.closeTracker != null) { // this was false alarm, we don't need to track the resource localVariableBinding.closeTracker.withdraw(); // keep the tracking variable in the resourceBinding in order to prevent creating a new one while analyzing the try block } } MethodBinding closeMethod = findCloseMethod(resource, resolvedType); if (closeMethod != null && closeMethod.isValidBinding() && 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], tryInfo, currentScope, true); } } } if (!this.tryBlock.isEmptyBlock()) { tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, tryInfo); if ((tryInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) this.bits |= ASTNode.IsTryBlockExiting; } if (resourcesLength > 0) { this.postTryInitStateIndex = currentScope.methodScope().recordInitializationStates(tryInfo); // the resources are not in scope after the try block, so remove their assignment info // to avoid polluting the state indices. However, do this after the postTryInitStateIndex is calculated since // it is used to add or remove assigned resources during code gen for (int i = 0; i < resourcesLength; i++) { if (this.resources[i] instanceof LocalDeclaration) tryInfo.resetAssignmentInfo(((LocalDeclaration)this.resources[i]).binding); } } // 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 = prepareCatchInfo(flowInfo, handlingContext, tryInfo, i); insideSubContext.conditionalLevel = 1; 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( ((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). addNullInfoFrom( handlingContext.initsOnFinally), currentScope); // chain up null info registry flowContext.mergeFinallyNullInfo(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; } } } private MethodBinding findCloseMethod(final ASTNode resource, TypeBinding type) { MethodBinding closeMethod = null; if (type != null && type.isValidBinding() && type instanceof ReferenceBinding) { ReferenceBinding binding = (ReferenceBinding) type; closeMethod = binding.getExactMethod(ConstantPool.Close, new TypeBinding [0], this.scope.compilationUnitScope()); // scope needs to be tighter if(closeMethod == null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=380112 // closeMethod could be null if the binding is from an interface // extending from multiple interfaces. InvocationSite site = new InvocationSite.EmptyWithAstNode(resource); closeMethod = this.scope.compilationUnitScope().findMethod(binding, ConstantPool.Close, new TypeBinding[0], site, false); } } return closeMethod; } private FlowInfo prepareCatchInfo(FlowInfo flowInfo, ExceptionHandlingFlowContext handlingContext, FlowInfo tryInfo, int i) { FlowInfo catchInfo; if (isUncheckedCatchBlock(i)) { catchInfo = flowInfo.unconditionalCopy(). addPotentialInitializationsFrom( handlingContext.initsOnException(i)). addPotentialInitializationsFrom(tryInfo). addPotentialInitializationsFrom( handlingContext.initsOnReturn). addNullInfoFrom(handlingContext.initsOnFinally); } else { FlowInfo initsOnException = handlingContext.initsOnException(i); catchInfo = flowInfo.nullInfoLessUnconditionalCopy() .addPotentialInitializationsFrom(initsOnException) .addNullInfoFrom(initsOnException) // <<== Null info only from here! .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 == null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=350579 catchInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD); } return catchInfo; } // 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; } @Override public ExceptionLabel enterAnyExceptionHandler(CodeStream codeStream) { if (this.subRoutineStartLabel == null) return null; return super.enterAnyExceptionHandler(codeStream); } @Override public void enterDeclaredExceptionHandlers(CodeStream codeStream) { for (int i = 0, length = this.declaredExceptionLabels == null ? 0 : this.declaredExceptionLabels.length; i < length; i++) { this.declaredExceptionLabels[i].placeStart(); } int resourceCount = this.resources.length; if (resourceCount > 0 && this.resourceExceptionLabels != null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=375248 // Reinstall handlers for (int i = resourceCount; i >= 0; --i) { this.resourceExceptionLabels[i].placeStart(); } } } @Override public void exitAnyExceptionHandler() { if (this.subRoutineStartLabel == null) return; super.exitAnyExceptionHandler(); } @Override 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
/** * 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 */
@Override 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++) { Argument argument = this.catchArguments[i]; ExceptionLabel exceptionLabel = null; if ((argument.binding.tagBits & TagBits.MultiCatchParameter) != 0) { MultiCatchExceptionLabel multiCatchExceptionLabel = new MultiCatchExceptionLabel(codeStream, argument.binding.type); multiCatchExceptionLabel.initialize((UnionTypeReference) argument.type, argument.annotations); exceptionLabel = multiCatchExceptionLabel; } else { exceptionLabel = new ExceptionLabel(codeStream, argument.binding.type, argument.type, argument.annotations); } 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; int resourceCount = this.resources.length; if (resourceCount > 0) { // Please see https://bugs.eclipse.org/bugs/show_bug.cgi?id=338402#c16 this.resourceExceptionLabels = new ExceptionLabel[resourceCount + 1]; codeStream.aconst_null(); codeStream.store(this.primaryExceptionVariable, false /* value not required */); codeStream.addVariable(this.primaryExceptionVariable); codeStream.aconst_null(); codeStream.store(this.caughtThrowableVariable, false /* value not required */); codeStream.addVariable(this.caughtThrowableVariable); 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) this.resourceExceptionLabels[i] = new ExceptionLabel(codeStream, null); this.resourceExceptionLabels[i].placeStart(); if (i < resourceCount) { Statement stmt = this.resources[i]; if (stmt instanceof NameReference) { NameReference ref = (NameReference) stmt; ref.bits |= ASTNode.IsCapturedOuterLocal; // TODO: selective flagging if ref.binding is not one of earlier inlined LVBs. VariableBinding binding = (VariableBinding) ref.binding; // Only LVB expected here. ref.checkEffectiveFinality(binding, this.scope); } else if (stmt instanceof FieldReference) { FieldReference fieldReference = (FieldReference) stmt; if (!fieldReference.binding.isFinal()) this.scope.problemReporter().cannotReferToNonFinalField(fieldReference.binding, fieldReference); } stmt.generateCode(this.scope, codeStream); // Initialize resources ... } } } this.tryBlock.generateCode(this.scope, codeStream); if (resourceCount > 0) { for (int i = resourceCount; i >= 0; i--) { BranchLabel exitLabel = new BranchLabel(codeStream); this.resourceExceptionLabels[i].placeEnd(); // outer handler if any is the one that should catch exceptions out of close() Statement stmt = i > 0 ? this.resources[i - 1] : null; if ((this.bits & ASTNode.IsTryBlockExiting) == 0) { // inline resource closure if (i > 0) { int invokeCloseStartPc = codeStream.position; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=343785 if (this.postTryInitStateIndex != -1) { /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=361053, we are just past a synthetic instance of try-catch-finally. Our initialization type state is the same as it was at the end of the just concluded try (catch rethrows) */ codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.postTryInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.postTryInitStateIndex); } generateCodeSnippet(stmt, codeStream, exitLabel, false /* record */); codeStream.recordPositionsFrom(invokeCloseStartPc, this.tryBlock.sourceEnd); } codeStream.goto_(exitLabel); // skip over the catch block. } if (i > 0) { // i is off by one codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.postResourcesInitStateIndexes[i - 1]); codeStream.addDefinitelyAssignedVariables(currentScope, this.postResourcesInitStateIndexes[i - 1]); } else { // For the first resource, its preset state is the preTryInitStateIndex codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.preTryInitStateIndex); } codeStream.pushExceptionOnStack(this.scope.getJavaLangThrowable()); this.resourceExceptionLabels[i].place(); if (i == resourceCount) { // inner most try's catch/finally can be a lot simpler. codeStream.store(this.primaryExceptionVariable, false); // fall through, invoke close() and re-throw. } else { BranchLabel elseLabel = new BranchLabel(codeStream), postElseLabel = new BranchLabel(codeStream); codeStream.store(this.caughtThrowableVariable, false); codeStream.load(this.primaryExceptionVariable); codeStream.ifnonnull(elseLabel); codeStream.load(this.caughtThrowableVariable); codeStream.store(this.primaryExceptionVariable, false); codeStream.goto_(postElseLabel); elseLabel.place(); codeStream.load(this.primaryExceptionVariable); codeStream.load(this.caughtThrowableVariable); codeStream.if_acmpeq(postElseLabel); codeStream.load(this.primaryExceptionVariable); codeStream.load(this.caughtThrowableVariable); codeStream.invokeThrowableAddSuppressed(); postElseLabel.place(); } if (i > 0) { // inline resource close here rather than bracketing the current catch block with a try region. BranchLabel postCloseLabel = new BranchLabel(codeStream); generateCodeSnippet(stmt, codeStream, postCloseLabel, true /* record */, i, codeStream.position); postCloseLabel.place(); } codeStream.load(this.primaryExceptionVariable); codeStream.athrow(); exitLabel.place(); } codeStream.removeVariable(this.primaryExceptionVariable); codeStream.removeVariable(this.caughtThrowableVariable); } } finally { this.declaredExceptionLabels = null; this.resourceExceptionLabels = null; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=375248 } 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.recordPositionsFrom(position, this.tryBlock.sourceEnd); //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].getCount() == 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.getCount() != 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); } private void generateCodeSnippet(Statement statement, CodeStream codeStream, BranchLabel postCloseLabel, boolean record, int... values) { int i = -1; int invokeCloseStartPc = -1; if (record) { i = values[0]; invokeCloseStartPc = values[1]; } if (statement instanceof LocalDeclaration) generateCodeSnippet((LocalDeclaration)statement, codeStream, postCloseLabel, record, i, invokeCloseStartPc); else if (statement instanceof Reference) generateCodeSnippet((Reference)statement, codeStream, postCloseLabel, record, i, invokeCloseStartPc); // else abort } private void generateCodeSnippet(Reference reference, CodeStream codeStream, BranchLabel postCloseLabel, boolean record, int i, int invokeCloseStartPc) { reference.generateCode(this.scope, codeStream, true); codeStream.ifnull(postCloseLabel); reference.generateCode(this.scope, codeStream, true); codeStream.invokeAutoCloseableClose(reference.resolvedType); if (!record) return; codeStream.recordPositionsFrom(invokeCloseStartPc, this.tryBlock.sourceEnd); isDuplicateResourceReference(i); } private void generateCodeSnippet(LocalDeclaration localDeclaration, CodeStream codeStream, BranchLabel postCloseLabel, boolean record, int i, int invokeCloseStartPc) { LocalVariableBinding variableBinding = localDeclaration.binding; codeStream.load(variableBinding); codeStream.ifnull(postCloseLabel); codeStream.load(variableBinding); codeStream.invokeAutoCloseableClose(variableBinding.type); if (!record) return; codeStream.recordPositionsFrom(invokeCloseStartPc, this.tryBlock.sourceEnd); if (!isDuplicateResourceReference(i)) // do not remove duplicate variable now codeStream.removeVariable(variableBinding); } private boolean isDuplicateResourceReference(int index) { int len = this.resources.length; if (index < len && this.resources[index] instanceof Reference) { Reference ref = (Reference) this.resources[index]; Binding refBinding = ref instanceof NameReference ? ((NameReference) ref).binding : ref instanceof FieldReference ? ((FieldReference) ref).binding : null; if (refBinding == null) return false; //TODO: For field accesses in the form of a.b.c and b.c - could there be a non-trivial dup - to check? for (int i = 0; i < index; i++) { Statement stmt = this.resources[i]; Binding b = stmt instanceof LocalDeclaration ? ((LocalDeclaration) stmt).binding : stmt instanceof NameReference ? ((NameReference) stmt).binding : stmt instanceof FieldReference ? ((FieldReference) stmt).binding : null; if (b == refBinding) { this.scope.problemReporter().duplicateResourceReference(ref); return true; } } } return false; }
See Also:
  • generateSubRoutineInvocation.generateSubRoutineInvocation(BlockScope, CodeStream, Object, int, LocalVariableBinding)
/** * @see SubRoutineStatement#generateSubRoutineInvocation(BlockScope, CodeStream, Object, int, LocalVariableBinding) */
@Override public boolean generateSubRoutineInvocation(BlockScope currentScope, CodeStream codeStream, Object targetLocation, int stateIndex, LocalVariableBinding secretLocal) { int resourceCount = this.resources.length; if (resourceCount > 0 && this.resourceExceptionLabels != null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=375248 for (int i = resourceCount; i > 0; --i) { // Disarm the handlers and take care of resource closure. this.resourceExceptionLabels[i].placeEnd(); BranchLabel exitLabel = new BranchLabel(codeStream); int invokeCloseStartPc = codeStream.position; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=343785 generateCodeSnippet(this.resources[i - 1], codeStream, exitLabel, false); codeStream.recordPositionsFrom(invokeCloseStartPc, this.tryBlock.sourceEnd); exitLabel.place(); } this.resourceExceptionLabels[0].placeEnd(); // outermost should end here as well, will start again on enter } 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) CompilerOptions options = this.scope.compilerOptions(); if (options.shareCommonFinallyBlocks && 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); } // 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; } @Override public boolean isSubRoutineEscaping() { return (this.bits & ASTNode.IsSubRoutineEscaping) != 0; } @Override 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++) { Statement stmt = this.resources[i]; if (stmt instanceof LocalDeclaration) { ((LocalDeclaration) stmt).printAsExpression(0, output); } else if (stmt instanceof Reference) { ((Reference) stmt).printExpression(0, output); } else continue; 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; } @Override 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); if (this.resources[i] instanceof LocalDeclaration) { LocalDeclaration node = (LocalDeclaration)this.resources[i]; LocalVariableBinding localVariableBinding = node.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, node.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, node.type); localVariableBinding.type = new ProblemReferenceBinding(CharOperation.splitOn('.', resourceType.shortReadableName()), null, ProblemReasons.InvalidTypeForAutoManagedResource); } } } else { // expression Expression node = (Expression) this.resources[i]; TypeBinding resourceType = node.resolvedType; if (resourceType instanceof ReferenceBinding) { if (resourceType.findSuperTypeOriginatingFrom(TypeIds.T_JavaLangAutoCloseable, false /*AutoCloseable is not a class*/) == null && resourceType.isValidBinding()) { upperScope.problemReporter().resourceHasToImplementAutoCloseable(resourceType, node); ((Expression) this.resources[i]).resolvedType = 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, node); ((Expression) this.resources[i]).resolvedType = 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 = methodScope.referenceContext instanceof AbstractMethodDeclaration ? ((AbstractMethodDeclaration) methodScope.referenceContext).binding : (methodScope.referenceContext instanceof LambdaExpression ? ((LambdaExpression)methodScope.referenceContext).binding : null); 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); } } @Override public void traverse(ASTVisitor visitor, BlockScope blockScope) { if (visitor.visit(this, blockScope)) { Statement[] statements = this.resources; for (int i = 0, max = statements.length; i < max; i++) { statements[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++) { if (argumentTypes[i] instanceof ArrayBinding) continue; 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]; if (currentExceptions == null) continue; 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]; if (exceptions == null) continue; 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++) { if (argumentTypes[i] instanceof ArrayBinding) continue; 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]); } } } } } @Override public boolean doesNotCompleteNormally() { if (!this.tryBlock.doesNotCompleteNormally()) { return (this.finallyBlock != null) ? this.finallyBlock.doesNotCompleteNormally() : false; } if (this.catchBlocks != null) { for (int i = 0; i < this.catchBlocks.length; i++) { if (!this.catchBlocks[i].doesNotCompleteNormally()) { return (this.finallyBlock != null) ? this.finallyBlock.doesNotCompleteNormally() : false; } } } return true; } @Override public boolean completesByContinue() { if (this.tryBlock.completesByContinue()) { return (this.finallyBlock == null) ? true : !this.finallyBlock.doesNotCompleteNormally() || this.finallyBlock.completesByContinue(); } if (this.catchBlocks != null) { for (int i = 0; i < this.catchBlocks.length; i++) { if (this.catchBlocks[i].completesByContinue()) { return (this.finallyBlock == null) ? true : !this.finallyBlock.doesNotCompleteNormally() || this.finallyBlock.completesByContinue(); } } } return this.finallyBlock != null && this.finallyBlock.completesByContinue(); } }