| /******************************************************************************* |
| * Copyright (c) 2000, 2015 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 319201 - [null] no warning when unboxing SingleNameReference causes NPE |
| * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" |
| * bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking |
| * Bug 415790 - [compiler][resource]Incorrect potential resource leak warning in for loop with close in try/catch |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.impl.*; |
| import org.eclipse.jdt.internal.compiler.codegen.*; |
| import org.eclipse.jdt.internal.compiler.flow.*; |
| import org.eclipse.jdt.internal.compiler.lookup.*; |
| |
| public class DoStatement extends Statement { |
| |
| public Expression condition; |
| public Statement action; |
| |
| private BranchLabel breakLabel, continueLabel; |
| |
| // for local variables table attributes |
| int mergedInitStateIndex = -1; |
| int preConditionInitStateIndex = -1; |
| |
| public DoStatement(Expression condition, Statement action, int sourceStart, int sourceEnd) { |
| |
| this.sourceStart = sourceStart; |
| this.sourceEnd = sourceEnd; |
| this.condition = condition; |
| this.action = action; |
| // remember useful empty statement |
| if (action instanceof EmptyStatement) action.bits |= ASTNode.IsUsefulEmptyStatement; |
| } |
| |
| @Override |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { |
| this.breakLabel = new BranchLabel(); |
| this.continueLabel = new BranchLabel(); |
| LoopingFlowContext loopingContext = |
| new LoopingFlowContext( |
| flowContext, |
| flowInfo, |
| this, |
| this.breakLabel, |
| this.continueLabel, |
| currentScope, |
| false); |
| |
| Constant cst = this.condition.constant; |
| boolean isConditionTrue = cst != Constant.NotAConstant && cst.booleanValue() == true; |
| cst = this.condition.optimizedBooleanConstant(); |
| boolean isConditionOptimizedTrue = cst != Constant.NotAConstant && cst.booleanValue() == true; |
| boolean isConditionOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false; |
| |
| int previousMode = flowInfo.reachMode(); |
| |
| FlowInfo initsOnCondition = flowInfo; |
| UnconditionalFlowInfo actionInfo = flowInfo.nullInfoLessUnconditionalCopy(); |
| // we need to collect the contribution to nulls of the coming paths through the |
| // loop, be they falling through normally or branched to break, continue labels |
| // or catch blocks |
| if ((this.action != null) && !this.action.isEmptyBlock()) { |
| actionInfo = this.action. |
| analyseCode(currentScope, loopingContext, actionInfo). |
| unconditionalInits(); |
| |
| // code generation can be optimized when no need to continue in the loop |
| if ((actionInfo.tagBits & |
| loopingContext.initsOnContinue.tagBits & |
| FlowInfo.UNREACHABLE_OR_DEAD) != 0) { |
| this.continueLabel = null; |
| } |
| if ((this.condition.implicitConversion & TypeIds.UNBOXING) != 0) { |
| initsOnCondition = flowInfo.unconditionalInits(). |
| addInitializationsFrom( |
| actionInfo.mergedWith(loopingContext.initsOnContinue)); |
| } |
| } |
| this.condition.checkNPEbyUnboxing(currentScope, flowContext, initsOnCondition); |
| /* Reset reach mode, to address following scenario. |
| * final blank; |
| * do { if (true) break; else blank = 0; } while(false); |
| * blank = 1; // may be initialized already |
| */ |
| actionInfo.setReachMode(previousMode); |
| |
| LoopingFlowContext condLoopContext; |
| FlowInfo condInfo = |
| this.condition.analyseCode( |
| currentScope, |
| (condLoopContext = |
| new LoopingFlowContext(flowContext, flowInfo, this, null, |
| null, currentScope, true)), |
| (this.action == null |
| ? actionInfo |
| : (actionInfo.mergedWith(loopingContext.initsOnContinue))).copy()); |
| /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=367023, we reach the condition at the bottom via two arcs, |
| one by free fall and another by continuing... Merge initializations propagated through the two pathways, |
| cf, while and for loops. |
| */ |
| this.preConditionInitStateIndex = currentScope.methodScope().recordInitializationStates(actionInfo.mergedWith(loopingContext.initsOnContinue)); |
| if (!isConditionOptimizedFalse && this.continueLabel != null) { |
| loopingContext.complainOnDeferredFinalChecks(currentScope, condInfo); |
| condLoopContext.complainOnDeferredFinalChecks(currentScope, condInfo); |
| loopingContext.complainOnDeferredNullChecks(currentScope, |
| flowInfo.unconditionalCopy().addPotentialNullInfoFrom( |
| condInfo.initsWhenTrue().unconditionalInits())); |
| condLoopContext.complainOnDeferredNullChecks(currentScope, |
| actionInfo.addPotentialNullInfoFrom( |
| condInfo.initsWhenTrue().unconditionalInits())); |
| } else { |
| loopingContext.complainOnDeferredNullChecks(currentScope, |
| flowInfo.unconditionalCopy().addPotentialNullInfoFrom( |
| condInfo.initsWhenTrue().unconditionalInits()), false); |
| condLoopContext.complainOnDeferredNullChecks(currentScope, |
| actionInfo.addPotentialNullInfoFrom( |
| condInfo.initsWhenTrue().unconditionalInits()), false); |
| } |
| if (loopingContext.hasEscapingExceptions()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=321926 |
| FlowInfo loopbackFlowInfo = flowInfo.copy(); |
| // loopback | (loopback + action + condition): |
| loopbackFlowInfo = loopbackFlowInfo.mergedWith(loopbackFlowInfo.unconditionalCopy().addNullInfoFrom(condInfo.initsWhenTrue()).unconditionalInits()); |
| loopingContext.simulateThrowAfterLoopBack(loopbackFlowInfo); |
| } |
| // end of loop |
| FlowInfo mergedInfo = |
| FlowInfo.mergedOptimizedBranches( |
| (loopingContext.initsOnBreak.tagBits & FlowInfo.UNREACHABLE) != 0 |
| ? loopingContext.initsOnBreak |
| : flowInfo.unconditionalCopy().addInitializationsFrom(loopingContext.initsOnBreak), |
| // recover upstream null info |
| isConditionOptimizedTrue, |
| (condInfo.tagBits & FlowInfo.UNREACHABLE) == 0 |
| ? flowInfo.copy().addInitializationsFrom(condInfo.initsWhenFalse()) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=380927 |
| : condInfo, |
| // recover null inits from before condition analysis |
| false, // never consider opt false case for DO loop, since break can always occur (47776) |
| !isConditionTrue /*do{}while(true); unreachable(); */); |
| this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); |
| return mergedInfo; |
| } |
| |
| /** |
| * Do statement code generation |
| * |
| */ |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream) { |
| if ((this.bits & ASTNode.IsReachable) == 0) { |
| return; |
| } |
| int pc = codeStream.position; |
| |
| // labels management |
| BranchLabel actionLabel = new BranchLabel(codeStream); |
| if (this.action != null) actionLabel.tagBits |= BranchLabel.USED; |
| actionLabel.place(); |
| this.breakLabel.initialize(codeStream); |
| boolean hasContinueLabel = this.continueLabel != null; |
| if (hasContinueLabel) { |
| this.continueLabel.initialize(codeStream); |
| } |
| |
| // generate action |
| if (this.action != null) { |
| this.action.generateCode(currentScope, codeStream); |
| } |
| // continue label (135602) |
| if (hasContinueLabel) { |
| this.continueLabel.place(); |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.preConditionInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preConditionInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.preConditionInitStateIndex); |
| } |
| // generate condition |
| Constant cst = this.condition.optimizedBooleanConstant(); |
| boolean isConditionOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false; |
| if (isConditionOptimizedFalse){ |
| this.condition.generateCode(currentScope, codeStream, false); |
| } else { |
| this.condition.generateOptimizedBoolean( |
| currentScope, |
| codeStream, |
| actionLabel, |
| null, |
| true); |
| } |
| } |
| // 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); |
| } |
| if (this.breakLabel.forwardReferenceCount() > 0) { |
| this.breakLabel.place(); |
| } |
| |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } |
| |
| @Override |
| public StringBuffer printStatement(int indent, StringBuffer output) { |
| printIndent(indent, output).append("do"); //$NON-NLS-1$ |
| if (this.action == null) |
| output.append(" ;\n"); //$NON-NLS-1$ |
| else { |
| output.append('\n'); |
| this.action.printStatement(indent + 1, output).append('\n'); |
| } |
| output.append("while ("); //$NON-NLS-1$ |
| return this.condition.printExpression(0, output).append(");"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void resolve(BlockScope scope) { |
| TypeBinding type = this.condition.resolveTypeExpecting(scope, TypeBinding.BOOLEAN); |
| this.condition.computeConversion(scope, type, type); |
| if (this.action != null) |
| this.action.resolve(scope); |
| } |
| |
| @Override |
| public void traverse(ASTVisitor visitor, BlockScope scope) { |
| if (visitor.visit(this, scope)) { |
| if (this.action != null) { |
| this.action.traverse(visitor, scope); |
| } |
| this.condition.traverse(visitor, scope); |
| } |
| visitor.endVisit(this, scope); |
| } |
| |
| @Override |
| public boolean doesNotCompleteNormally() { |
| Constant cst = this.condition.constant; |
| boolean isConditionTrue = cst == null || cst != Constant.NotAConstant && cst.booleanValue() == true; |
| cst = this.condition.optimizedBooleanConstant(); |
| boolean isConditionOptimizedTrue = cst == null ? true : cst != Constant.NotAConstant && cst.booleanValue() == true; |
| |
| if (isConditionTrue || isConditionOptimizedTrue) |
| return this.action == null || !this.action.breaksOut(null); |
| if (this.action == null || this.action.breaksOut(null)) |
| return false; |
| return this.action.doesNotCompleteNormally() && !this.action.completesByContinue(); |
| } |
| |
| @Override |
| public boolean completesByContinue() { |
| return this.action.continuesAtOuterLabel(); |
| } |
| } |