Java tutorial
/******************************************************************************* * 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 */ @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 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(); } }