/*******************************************************************************
* Copyright (c) 2000, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for
* o inline call that is used in a field initializer
* (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38137)
* Benjamin Muskalla <bmuskalla@eclipsesource.com> - [extract method] Missing return value,
* while extracting code out of a loop - https://bugs.eclipse.org/bugs/show_bug.cgi?id=213519
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.corext.refactoring.code.flow;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.IRegion;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTConditionalExpression;
import org.eclipse.cdt.core.dom.ast.IASTDoStatement;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTForStatement;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTGotoStatement;
import org.eclipse.cdt.core.dom.ast.IASTIfStatement;
import org.eclipse.cdt.core.dom.ast.IASTLabelStatement;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTReturnStatement;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
import org.eclipse.cdt.core.dom.ast.IASTWhileStatement;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTRangeBasedForStatement;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics;
import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes;
public class InputFlowAnalyzer extends FlowAnalyzer {
private static class LoopReentranceVisitor extends FlowAnalyzer {
private final Selection selection;
private final IASTNode loopNode;
public LoopReentranceVisitor(FlowContext context, Selection selection, IASTNode loopNode) {
super(context);
this.selection= selection;
this.loopNode= loopNode;
}
@Override
protected boolean traverseNode(IASTNode node) {
return true; // end <= fSelection.end || fSelection.enclosedBy(start, end);
}
@Override
protected boolean shouldCreateReturnFlowInfo(IASTReturnStatement node) {
// Make sure that the whole return statement is selected or located before the selection.
return ASTNodes.endOffset(node) <= selection.getEnd();
}
protected IASTNode getLoopNode() {
return loopNode;
}
public void process(IASTNode node) {
try {
fFlowContext.setLoopReentranceMode(true);
node.accept(this);
} finally {
fFlowContext.setLoopReentranceMode(false);
}
}
@Override
public int leave(IASTDoStatement node) {
if (skipNode(node))
return PROCESS_SKIP;
DoWhileFlowInfo info= createDoWhile();
setFlowInfo(node, info);
info.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
// No need to merge the condition. It was already considered by the InputFlowAnalyzer.
info.removeLabel(null);
return PROCESS_SKIP;
}
@Override
public int leave(ICPPASTRangeBasedForStatement node) {
if (skipNode(node))
return PROCESS_SKIP;
FlowInfo paramInfo= getFlowInfo(node.getDeclaration());
FlowInfo expressionInfo= getFlowInfo(node.getInitializerClause());
FlowInfo actionInfo= getFlowInfo(node.getBody());
RangeBasedForFlowInfo forInfo= createRangeBasedFor();
setFlowInfo(node, forInfo);
// If the for statement is the outermost loop then we only have to consider
// the action. The parameter and expression are only evaluated once.
if (node == loopNode) {
forInfo.mergeAction(actionInfo, fFlowContext);
} else {
// Inner for loops are evaluated in the sequence expression, parameter,
// action.
forInfo.mergeInitializerClause(expressionInfo, fFlowContext);
forInfo.mergeDeclaration(paramInfo, fFlowContext);
forInfo.mergeAction(actionInfo, fFlowContext);
}
forInfo.removeLabel(null);
return PROCESS_SKIP;
}
@Override
public int leave(IASTForStatement node) {
if (skipNode(node))
return PROCESS_SKIP;
FlowInfo initInfo= createSequential(node.getInitializerStatement());
FlowInfo conditionInfo= getFlowInfo(node.getConditionExpression());
FlowInfo incrementInfo= createSequential(node.getIterationExpression());
FlowInfo actionInfo= getFlowInfo(node.getBody());
ForFlowInfo forInfo= createFor();
setFlowInfo(node, forInfo);
// The for statement is the outermost loop. In this case we only have
// to consider the increment, condition and action.
if (node == loopNode) {
forInfo.mergeIncrement(incrementInfo, fFlowContext);
forInfo.mergeCondition(conditionInfo, fFlowContext);
forInfo.mergeAction(actionInfo, fFlowContext);
} else {
// We have to merge two different cases. One if we reenter the for statement
// immediately (that means we have to consider increments, condition and action)
// and the other case if we reenter the for in the next loop of
// the outer loop. Then we have to consider initializations, condition and action.
// For a conditional flow info that means:
// (initializations | increments) & condition & action.
GenericConditionalFlowInfo initIncr= new GenericConditionalFlowInfo();
initIncr.merge(initInfo, fFlowContext);
initIncr.merge(incrementInfo, fFlowContext);
forInfo.mergeAccessModeSequential(initIncr, fFlowContext);
forInfo.mergeCondition(conditionInfo, fFlowContext);
forInfo.mergeAction(actionInfo, fFlowContext);
}
forInfo.removeLabel(null);
return PROCESS_SKIP;
}
}
private static class GotoLoopRegion implements IRegion {
final IASTLabelStatement firstLabelStatement;
IASTGotoStatement lastGotoStatement;
GotoLoopRegion(IASTLabelStatement firstLabelStatement, IASTGotoStatement lastGotoStatement) {
this.firstLabelStatement = firstLabelStatement;
this.lastGotoStatement = lastGotoStatement;
}
@Override
public int getOffset() {
return ASTNodes.offset(firstLabelStatement);
}
@Override
public int getLength() {
return ASTNodes.endOffset(lastGotoStatement) - getOffset();
}
}
private static class GotoAnalyzer extends ASTVisitor {
private GotoLoopRegion[] gotoRegions = {};
public GotoAnalyzer() {
shouldVisitStatements = true;
}
@Override
public int visit(IASTStatement node) {
if (!(node instanceof IASTLabelStatement))
return PROCESS_CONTINUE;
IASTLabelStatement labelStatement = (IASTLabelStatement) node;
IASTName labelName = ((IASTLabelStatement) node).getName();
IBinding binding = labelName.resolveBinding();
IASTName[] references = labelStatement.getTranslationUnit().getReferences(binding);
IASTGotoStatement lastGotoStatement = null;
for (IASTName name : references) {
if (name.getPropertyInParent() == IASTGotoStatement.NAME) {
IASTGotoStatement gotoStatement = (IASTGotoStatement) name.getParent();
IASTStatement lastStatement = lastGotoStatement != null ? lastGotoStatement : labelStatement;
if (isBefore(lastStatement, gotoStatement)) {
lastGotoStatement = gotoStatement;
}
}
}
if (lastGotoStatement == null)
return PROCESS_CONTINUE;
GotoLoopRegion newRegion = new GotoLoopRegion(labelStatement, lastGotoStatement);
boolean gaps = false;
for (int i = 0; i < gotoRegions.length; i++) {
GotoLoopRegion existing = gotoRegions[i];
if (existing == null)
break;
if (isBefore(newRegion.firstLabelStatement, existing.lastGotoStatement)) {
if (isBefore(existing.lastGotoStatement, newRegion.lastGotoStatement)) {
existing.lastGotoStatement = newRegion.lastGotoStatement;
for (int j = i + 1; j < gotoRegions.length; j++) {
newRegion = gotoRegions[j];
if (newRegion == null)
break;
if (isBefore(newRegion.firstLabelStatement, existing.lastGotoStatement)) {
if (isBefore(existing.lastGotoStatement, newRegion.lastGotoStatement)) {
existing.lastGotoStatement = newRegion.lastGotoStatement;
}
}
gotoRegions[j] = null;
gaps = true;
}
}
newRegion = null;
break;
}
}
if (gaps) {
ArrayUtil.compact(gotoRegions);
} else if (newRegion != null) {
gotoRegions = ArrayUtil.append(gotoRegions, newRegion);
}
return PROCESS_SKIP;
}
public GotoLoopRegion[] getGotoRegions() {
return ArrayUtil.trim(gotoRegions);
}
}
private static class GotoReentranceVisitor extends FlowAnalyzer {
private final Selection selection;
private final GotoLoopRegion loopRegion;
public GotoReentranceVisitor(FlowContext context, Selection selection, GotoLoopRegion loopRegion) {
super(context);
this.selection= selection;
this.loopRegion = loopRegion;
}
@Override
protected boolean traverseNode(IASTNode node) {
return !selection.covers(node) && !isBefore(node, loopRegion.firstLabelStatement) &&
!isBefore(loopRegion.lastGotoStatement, node);
}
@Override
protected boolean shouldCreateReturnFlowInfo(IASTReturnStatement node) {
// Make sure that the whole return statement is selected or located before the selection.
return ASTNodes.endOffset(node) <= selection.getEnd();
}
public FlowInfo process(IASTFunctionDefinition node) {
node.accept(this);
return getFlowInfo(node);
}
}
private Selection fSelection;
private boolean fDoLoopReentrance;
private LoopReentranceVisitor fLoopReentranceVisitor;
private GotoReentranceVisitor fGotoReentranceVisitor;
public InputFlowAnalyzer(FlowContext context, Selection selection, boolean doLoopReentrance) {
super(context);
fSelection= selection;
Assert.isNotNull(fSelection);
fDoLoopReentrance= doLoopReentrance;
}
public FlowInfo perform(IASTFunctionDefinition node) {
node.accept(this);
if (fDoLoopReentrance) {
GotoAnalyzer gotoAnalyzer = new GotoAnalyzer();
node.accept(gotoAnalyzer);
GotoLoopRegion[] gotoRegions = gotoAnalyzer.getGotoRegions();
for (GotoLoopRegion loopRegion : gotoRegions) {
if (fSelection.coveredBy(loopRegion)) {
GenericSequentialFlowInfo info= createSequential();
info.merge(getFlowInfo(node), fFlowContext);
fGotoReentranceVisitor = new GotoReentranceVisitor(fFlowContext, fSelection, loopRegion);
FlowInfo gotoInfo = fGotoReentranceVisitor.process(node);
info.merge(gotoInfo, fFlowContext);
setFlowInfo(node, info);
break;
}
}
}
return getFlowInfo(node);
}
@Override
protected boolean traverseNode(IASTNode node) {
if (fSelection.covers(node))
return false;
if (node instanceof IASTLabelStatement)
return true;
return ASTNodes.endOffset(node) >= fSelection.getEnd();
}
@Override
protected boolean shouldCreateReturnFlowInfo(IASTReturnStatement node) {
// Make sure that the whole return statement is located after the selection.
// There can be cases like return i + (x + 10) * 10; In this case we must not create
// a return info node.
return ASTNodes.offset(node) >= fSelection.getEnd();
}
@Override
public int visit(IASTDoStatement node) {
createLoopReentranceVisitor(node);
return super.visit(node);
}
@Override
public int visit(ICPPASTRangeBasedForStatement node) {
createLoopReentranceVisitor(node);
return super.visit(node);
}
@Override
public int visit(IASTForStatement node) {
createLoopReentranceVisitor(node);
return super.visit(node);
}
@Override
public int visit(IASTWhileStatement node) {
createLoopReentranceVisitor(node);
return super.visit(node);
}
private void createLoopReentranceVisitor(IASTNode node) {
if (fLoopReentranceVisitor == null && fDoLoopReentrance && fSelection.coveredBy(node)) {
fLoopReentranceVisitor= new LoopReentranceVisitor(fFlowContext, fSelection, node);
}
}
@Override
public int leave(IASTConditionalExpression node) {
if (skipNode(node))
return PROCESS_SKIP;
IASTExpression thenPart= node.getPositiveResultExpression();
IASTExpression elsePart= node.getNegativeResultExpression();
if ((thenPart != null && fSelection.coveredBy(thenPart)) ||
(elsePart != null && fSelection.coveredBy(elsePart))) {
GenericSequentialFlowInfo info= createSequential();
setFlowInfo(node, info);
leaveConditional(info, node.getLogicalConditionExpression(), new IASTNode[] { thenPart, elsePart });
return PROCESS_SKIP;
}
return super.leave(node);
}
@Override
public int leave(IASTDoStatement node) {
super.leave(node);
handleLoopReentrance(node);
return PROCESS_SKIP;
}
@Override
public int leave(IASTIfStatement node) {
if (skipNode(node))
return PROCESS_SKIP;
IASTStatement thenPart= node.getThenClause();
IASTStatement elsePart= node.getElseClause();
if ((thenPart != null && fSelection.coveredBy(thenPart)) ||
(elsePart != null && fSelection.coveredBy(elsePart))) {
GenericSequentialFlowInfo info= createSequential();
setFlowInfo(node, info);
leaveConditional(info, node.getConditionExpression(), new IASTNode[] { thenPart, elsePart });
return PROCESS_SKIP;
}
return super.leave(node);
}
@Override
public int leave(ICPPASTRangeBasedForStatement node) {
super.leave(node);
handleLoopReentrance(node);
return PROCESS_SKIP;
}
@Override
public int leave(IASTForStatement node) {
super.leave(node);
handleLoopReentrance(node);
return PROCESS_SKIP;
}
@Override
public int leave(IASTSwitchStatement node) {
if (skipNode(node))
return PROCESS_SKIP;
SwitchData data= createSwitchData(node);
IRegion[] ranges= data.getRanges();
for (int i= 0; i < ranges.length; i++) {
IRegion range= ranges[i];
if (fSelection.coveredBy(range)) {
GenericSequentialFlowInfo info= createSequential();
setFlowInfo(node, info);
info.merge(getFlowInfo(node.getControllerExpression()), fFlowContext);
info.merge(data.getInfo(i), fFlowContext);
info.removeLabel(null);
return PROCESS_SKIP;
}
}
return super.leave(node, data);
}
@Override
public int leave(IASTWhileStatement node) {
super.leave(node);
handleLoopReentrance(node);
return PROCESS_SKIP;
}
private void leaveConditional(GenericSequentialFlowInfo info, IASTNode condition, IASTNode[] branches) {
info.merge(getFlowInfo(condition), fFlowContext);
for (IASTNode branch : branches) {
if (branch != null && fSelection.coveredBy(branch)) {
info.merge(getFlowInfo(branch), fFlowContext);
break;
}
}
}
private void handleLoopReentrance(IASTNode node) {
if (fLoopReentranceVisitor == null || fLoopReentranceVisitor.getLoopNode() != node)
return;
fLoopReentranceVisitor.process(node);
GenericSequentialFlowInfo info= createSequential();
info.merge(getFlowInfo(node), fFlowContext);
info.merge(fLoopReentranceVisitor.getFlowInfo(node), fFlowContext);
setFlowInfo(node, info);
}
private static boolean isBefore(IASTNode node1, IASTNode node2) {
return CPPSemantics.declaredBefore(node1, node2, false);
}
}