| /******************************************************************************* |
| * Copyright (c) 2000, 2014 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 349326 - [1.7] new warning for missing try-with-resources |
| * bug 345305 - [compiler][null] Compiler misidentifies a case of "variable can only be null" |
| * bug 383368 - [compiler][null] syntactic null analysis for field references |
| * bug 403147 - [compiler][null] FUP of bug 400761: consolidate interaction between unboxing, NPE, and deferred checking |
| *******************************************************************************/ |
| 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 IfStatement extends Statement { |
| |
| //this class represents the case of only one statement in |
| //either else and/or then branches. |
| |
| public Expression condition; |
| public Statement thenStatement; |
| public Statement elseStatement; |
| |
| // for local variables table attributes |
| int thenInitStateIndex = -1; |
| int elseInitStateIndex = -1; |
| int mergedInitStateIndex = -1; |
| |
| public IfStatement(Expression condition, Statement thenStatement, int sourceStart, int sourceEnd) { |
| this.condition = condition; |
| this.thenStatement = thenStatement; |
| // remember useful empty statement |
| if (thenStatement instanceof EmptyStatement) thenStatement.bits |= IsUsefulEmptyStatement; |
| this.sourceStart = sourceStart; |
| this.sourceEnd = sourceEnd; |
| } |
| |
| public IfStatement(Expression condition, Statement thenStatement, Statement elseStatement, int sourceStart, int sourceEnd) { |
| this.condition = condition; |
| this.thenStatement = thenStatement; |
| // remember useful empty statement |
| if (thenStatement instanceof EmptyStatement) thenStatement.bits |= IsUsefulEmptyStatement; |
| this.elseStatement = elseStatement; |
| if (elseStatement instanceof IfStatement) elseStatement.bits |= IsElseIfStatement; |
| if (elseStatement instanceof EmptyStatement) elseStatement.bits |= IsUsefulEmptyStatement; |
| this.sourceStart = sourceStart; |
| this.sourceEnd = sourceEnd; |
| } |
| |
| @Override |
| public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { |
| // process the condition |
| FlowInfo conditionFlowInfo = this.condition.analyseCode(currentScope, flowContext, flowInfo); |
| int initialComplaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; |
| |
| Constant cst = this.condition.optimizedBooleanConstant(); |
| this.condition.checkNPEbyUnboxing(currentScope, flowContext, flowInfo); |
| boolean isConditionOptimizedTrue = cst != Constant.NotAConstant && cst.booleanValue() == true; |
| boolean isConditionOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false; |
| |
| flowContext.conditionalLevel++; |
| |
| // process the THEN part |
| FlowInfo thenFlowInfo = conditionFlowInfo.safeInitsWhenTrue(); |
| if (isConditionOptimizedFalse) { |
| thenFlowInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD); |
| } |
| FlowInfo elseFlowInfo = conditionFlowInfo.initsWhenFalse().copy(); |
| if (isConditionOptimizedTrue) { |
| elseFlowInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD); |
| } |
| if (((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) && |
| ((thenFlowInfo.tagBits & FlowInfo.UNREACHABLE) != 0)) { |
| // Mark then block as unreachable |
| // No need if the whole if-else construct itself lies in unreachable code |
| this.bits |= ASTNode.IsThenStatementUnreachable; |
| } else if (((flowInfo.tagBits & FlowInfo.UNREACHABLE) == 0) && |
| ((elseFlowInfo.tagBits & FlowInfo.UNREACHABLE) != 0)) { |
| // Mark else block as unreachable |
| // No need if the whole if-else construct itself lies in unreachable code |
| this.bits |= ASTNode.IsElseStatementUnreachable; |
| } |
| boolean reportDeadCodeForKnownPattern = !isKnowDeadCodePattern(this.condition) || currentScope.compilerOptions().reportDeadCodeInTrivialIfStatement; |
| if (this.thenStatement != null) { |
| // Save info for code gen |
| this.thenInitStateIndex = currentScope.methodScope().recordInitializationStates(thenFlowInfo); |
| if (isConditionOptimizedFalse || ((this.bits & ASTNode.IsThenStatementUnreachable) != 0)) { |
| if (reportDeadCodeForKnownPattern) { |
| this.thenStatement.complainIfUnreachable(thenFlowInfo, currentScope, initialComplaintLevel, false); |
| } else { |
| // its a known coding pattern which should be tolerated by dead code analysis |
| // according to isKnowDeadCodePattern() |
| this.bits &= ~ASTNode.IsThenStatementUnreachable; |
| } |
| } |
| thenFlowInfo = this.thenStatement.analyseCode(currentScope, flowContext, thenFlowInfo); |
| if (!(this.thenStatement instanceof Block)) |
| flowContext.expireNullCheckedFieldInfo(); |
| } |
| // any null check from the condition is now expired |
| flowContext.expireNullCheckedFieldInfo(); |
| // code gen: optimizing the jump around the ELSE part |
| if ((thenFlowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) { |
| this.bits |= ASTNode.ThenExit; |
| } |
| |
| // process the ELSE part |
| if (this.elseStatement != null) { |
| // signal else clause unnecessarily nested, tolerate else-if code pattern |
| if (thenFlowInfo == FlowInfo.DEAD_END |
| && (this.bits & IsElseIfStatement) == 0 // else of an else-if |
| && !(this.elseStatement instanceof IfStatement)) { |
| //{ObjectTeams: silent for generated methods: |
| if (!currentScope.isGeneratedScope()) |
| // SH} |
| currentScope.problemReporter().unnecessaryElse(this.elseStatement); |
| } |
| // Save info for code gen |
| this.elseInitStateIndex = currentScope.methodScope().recordInitializationStates(elseFlowInfo); |
| if (isConditionOptimizedTrue || ((this.bits & ASTNode.IsElseStatementUnreachable) != 0)) { |
| if (reportDeadCodeForKnownPattern) { |
| this.elseStatement.complainIfUnreachable(elseFlowInfo, currentScope, initialComplaintLevel, false); |
| } else { |
| // its a known coding pattern which should be tolerated by dead code analysis |
| // according to isKnowDeadCodePattern() |
| this.bits &= ~ASTNode.IsElseStatementUnreachable; |
| } |
| } |
| elseFlowInfo = this.elseStatement.analyseCode(currentScope, flowContext, elseFlowInfo); |
| if (!(this.elseStatement instanceof Block)) |
| flowContext.expireNullCheckedFieldInfo(); |
| } |
| // process AutoCloseable resources closed in only one branch: |
| currentScope.correlateTrackingVarsIfElse(thenFlowInfo, elseFlowInfo); |
| // merge THEN & ELSE initializations |
| FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranchesIfElse( |
| thenFlowInfo, |
| isConditionOptimizedTrue, |
| elseFlowInfo, |
| isConditionOptimizedFalse, |
| true /*if(true){ return; } fake-reachable(); */, |
| flowInfo, |
| this, |
| reportDeadCodeForKnownPattern); |
| this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); |
| flowContext.conditionalLevel--; |
| return mergedInfo; |
| } |
| |
| /** |
| * If code generation |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| */ |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream) { |
| if ((this.bits & IsReachable) == 0) { |
| return; |
| } |
| int pc = codeStream.position; |
| BranchLabel endifLabel = new BranchLabel(codeStream); |
| |
| // optimizing the then/else part code gen |
| Constant cst; |
| boolean hasThenPart = |
| !(((cst = this.condition.optimizedBooleanConstant()) != Constant.NotAConstant |
| && cst.booleanValue() == false) |
| || this.thenStatement == null |
| || this.thenStatement.isEmptyBlock()); |
| boolean hasElsePart = |
| !((cst != Constant.NotAConstant && cst.booleanValue() == true) |
| || this.elseStatement == null |
| || this.elseStatement.isEmptyBlock()); |
| if (hasThenPart) { |
| BranchLabel falseLabel = null; |
| // generate boolean condition only if needed |
| if (cst != Constant.NotAConstant && cst.booleanValue() == true) { |
| this.condition.generateCode(currentScope, codeStream, false); |
| } else { |
| this.condition.generateOptimizedBoolean( |
| currentScope, |
| codeStream, |
| null, |
| hasElsePart ? (falseLabel = new BranchLabel(codeStream)) : endifLabel, |
| true/*cst == Constant.NotAConstant*/); |
| } |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.thenInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.thenInitStateIndex); |
| } |
| // generate then statement |
| this.thenStatement.generateCode(currentScope, codeStream); |
| // jump around the else statement |
| if (hasElsePart) { |
| if ((this.bits & ASTNode.ThenExit) == 0) { |
| this.thenStatement.branchChainTo(endifLabel); |
| int position = codeStream.position; |
| codeStream.goto_(endifLabel); |
| //goto is pointing to the last line of the thenStatement |
| codeStream.recordPositionsFrom(position, this.thenStatement.sourceEnd); |
| // generate else statement |
| } |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.elseInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables( |
| currentScope, |
| this.elseInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.elseInitStateIndex); |
| } |
| if (falseLabel != null) falseLabel.place(); |
| this.elseStatement.generateCode(currentScope, codeStream); |
| } |
| } else if (hasElsePart) { |
| // generate boolean condition only if needed |
| if (cst != Constant.NotAConstant && cst.booleanValue() == false) { |
| this.condition.generateCode(currentScope, codeStream, false); |
| } else { |
| this.condition.generateOptimizedBoolean( |
| currentScope, |
| codeStream, |
| endifLabel, |
| null, |
| true/*cst == Constant.NotAConstant*/); |
| } |
| // generate else statement |
| // May loose some local variable initializations : affecting the local variable attributes |
| if (this.elseInitStateIndex != -1) { |
| codeStream.removeNotDefinitelyAssignedVariables( |
| currentScope, |
| this.elseInitStateIndex); |
| codeStream.addDefinitelyAssignedVariables(currentScope, this.elseInitStateIndex); |
| } |
| this.elseStatement.generateCode(currentScope, codeStream); |
| } else { |
| // generate condition side-effects |
| this.condition.generateCode(currentScope, codeStream, false); |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } |
| // 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); |
| } |
| endifLabel.place(); |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| } |
| |
| |
| |
| @Override |
| public StringBuffer printStatement(int indent, StringBuffer output) { |
| printIndent(indent, output).append("if ("); //$NON-NLS-1$ |
| this.condition.printExpression(0, output).append(")\n"); //$NON-NLS-1$ |
| this.thenStatement.printStatement(indent + 2, output); |
| if (this.elseStatement != null) { |
| output.append('\n'); |
| printIndent(indent, output); |
| output.append("else\n"); //$NON-NLS-1$ |
| this.elseStatement.printStatement(indent + 2, output); |
| } |
| return output; |
| } |
| |
| @Override |
| public void resolve(BlockScope scope) { |
| TypeBinding type = this.condition.resolveTypeExpecting(scope, TypeBinding.BOOLEAN); |
| this.condition.computeConversion(scope, type, type); |
| if (this.thenStatement != null) |
| this.thenStatement.resolve(scope); |
| if (this.elseStatement != null) |
| this.elseStatement.resolve(scope); |
| } |
| |
| @Override |
| public void traverse(ASTVisitor visitor, BlockScope blockScope) { |
| if (visitor.visit(this, blockScope)) { |
| this.condition.traverse(visitor, blockScope); |
| if (this.thenStatement != null) |
| this.thenStatement.traverse(visitor, blockScope); |
| if (this.elseStatement != null) |
| this.elseStatement.traverse(visitor, blockScope); |
| } |
| visitor.endVisit(this, blockScope); |
| } |
| |
| @Override |
| public boolean doesNotCompleteNormally() { |
| return this.thenStatement != null && this.thenStatement.doesNotCompleteNormally() && this.elseStatement != null && this.elseStatement.doesNotCompleteNormally(); |
| } |
| @Override |
| public boolean completesByContinue() { |
| return this.thenStatement != null && this.thenStatement.completesByContinue() || this.elseStatement != null && this.elseStatement.completesByContinue(); |
| } |
| } |