| /******************************************************************************* |
| * 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 |
| * Fraunhofer FIRST - extended API and implementation |
| * Technical University Berlin - extended 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.*; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.DeclaredLifting; |
| |
| /** |
| * OTDT changes: support declared lifting for exception in catch block. |
| */ |
| 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; |
| //{ObjectTeams: accessible for anonymous sub-class: |
| protected |
| // SH} |
| 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 = |
| //{ObjectTeams: make hookable: |
| createFlowContext(flowContext, flowInfo.unconditionalInits()); |
| /* orig: |
| new ExceptionHandlingFlowContext( |
| flowContext, |
| this, |
| this.caughtExceptionTypes, |
| this.caughtExceptionsCatchBlocks, |
| null, |
| this.scope, |
| flowInfo); |
| :giro */ |
| // SH} |
| 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; |
| } |
| } |
| } |
| //{ObjectTeams: new hook for guard predicate analysis: |
| protected ExceptionHandlingFlowContext createFlowContext(FlowContext flowContext, FlowInfo flowInfo) { |
| return new ExceptionHandlingFlowContext( |
| flowContext, |
| this, |
| this.caughtExceptionTypes, |
| this.caughtExceptionsCatchBlocks, |
| null, |
| this.scope, |
| flowInfo); |
| } |
| // SH} |
| 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; |
| } |
| //{ObjectTeams: declared lifting: |
| if (this.catchArguments[i].type.isDeclaredLifting()) |
| DeclaredLifting.transformCatch(catchScope, this.catchBlocks[i], this.catchArguments[i]); |
| // SH} |
| // 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(); |
| } |
| } |