/*******************************************************************************
* Copyright (c) 2000, 2008 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
*******************************************************************************/
package org.eclipse.dltk.internal.javascript.corext.refactoring.code.flow;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.dltk.javascript.core.dom.ArrayAccessExpression;
import org.eclipse.dltk.javascript.core.dom.ArrayLiteral;
import org.eclipse.dltk.javascript.core.dom.BinaryExpression;
import org.eclipse.dltk.javascript.core.dom.BinaryOperator;
import org.eclipse.dltk.javascript.core.dom.BlockStatement;
import org.eclipse.dltk.javascript.core.dom.BreakStatement;
import org.eclipse.dltk.javascript.core.dom.CallExpression;
import org.eclipse.dltk.javascript.core.dom.CatchClause;
import org.eclipse.dltk.javascript.core.dom.ConditionalExpression;
import org.eclipse.dltk.javascript.core.dom.ConstStatement;
import org.eclipse.dltk.javascript.core.dom.ContinueStatement;
import org.eclipse.dltk.javascript.core.dom.DefaultClause;
import org.eclipse.dltk.javascript.core.dom.DoStatement;
import org.eclipse.dltk.javascript.core.dom.Expression;
import org.eclipse.dltk.javascript.core.dom.ExpressionStatement;
import org.eclipse.dltk.javascript.core.dom.ForEachInStatement;
import org.eclipse.dltk.javascript.core.dom.ForInStatement;
import org.eclipse.dltk.javascript.core.dom.ForStatement;
import org.eclipse.dltk.javascript.core.dom.FunctionExpression;
import org.eclipse.dltk.javascript.core.dom.GetterAssignment;
import org.eclipse.dltk.javascript.core.dom.IfStatement;
import org.eclipse.dltk.javascript.core.dom.Label;
import org.eclipse.dltk.javascript.core.dom.LabeledStatement;
import org.eclipse.dltk.javascript.core.dom.NewExpression;
import org.eclipse.dltk.javascript.core.dom.Node;
import org.eclipse.dltk.javascript.core.dom.ObjectLiteral;
import org.eclipse.dltk.javascript.core.dom.ParenthesizedExpression;
import org.eclipse.dltk.javascript.core.dom.PropertyAccessExpression;
import org.eclipse.dltk.javascript.core.dom.ReturnStatement;
import org.eclipse.dltk.javascript.core.dom.SetterAssignment;
import org.eclipse.dltk.javascript.core.dom.SimplePropertyAssignment;
import org.eclipse.dltk.javascript.core.dom.Source;
import org.eclipse.dltk.javascript.core.dom.Statement;
import org.eclipse.dltk.javascript.core.dom.SwitchElement;
import org.eclipse.dltk.javascript.core.dom.SwitchStatement;
import org.eclipse.dltk.javascript.core.dom.ThrowStatement;
import org.eclipse.dltk.javascript.core.dom.TryStatement;
import org.eclipse.dltk.javascript.core.dom.UnaryExpression;
import org.eclipse.dltk.javascript.core.dom.VariableDeclaration;
import org.eclipse.dltk.javascript.core.dom.VariableReference;
import org.eclipse.dltk.javascript.core.dom.VariableStatement;
import org.eclipse.dltk.javascript.core.dom.WhileStatement;
import org.eclipse.dltk.javascript.core.dom.rewrite.RefactoringUtils;
import org.eclipse.dltk.javascript.core.dom.util.DomSwitch;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
/**
* Special flow analyzer to determine the return value of the extracted method
* and the variables which have to be passed to the method.
*
* Note: This analyzer doesn't do a full flow analysis. For example it doesn't
* do dead code analysis or variable initialization analysis. It analyses the
* the first access to a variable (read or write) and if all execution paths
* return a value.
*/
abstract class FlowAnalyzer extends DomSwitch<Boolean> {
static protected class SwitchData {
private boolean fHasDefaultCase;
private List<IRegion> fRanges= new ArrayList<IRegion>(4);
private List<FlowInfo> fInfos= new ArrayList<FlowInfo>(4);
public void setHasDefaultCase() {
fHasDefaultCase= true;
}
public boolean hasDefaultCase() {
return fHasDefaultCase;
}
public void add(IRegion range, FlowInfo info) {
fRanges.add(range);
fInfos.add(info);
}
public IRegion[] getRanges() {
return (IRegion[]) fRanges.toArray(new IRegion[fRanges.size()]);
}
public FlowInfo[] getInfos() {
return (FlowInfo[]) fInfos.toArray(new FlowInfo[fInfos.size()]);
}
public FlowInfo getInfo(int index) {
return (FlowInfo)fInfos.get(index);
}
}
private Map<Node,FlowInfo> fData = new HashMap<Node,FlowInfo>(100);
private GenericConditionalFlowInfo callInfo = createGenericConditional();
public GenericConditionalFlowInfo closureInfo = createGenericConditional();
/* package */ FlowContext fFlowContext;
public FlowAnalyzer(FlowContext context) {
fFlowContext= context;
callInfo.mergeEmptyCondition(context);
}
protected abstract boolean createReturnFlowInfo(ReturnStatement node);
protected abstract boolean isTraverseNeeded(Node node);
/*protected boolean skipNode(ASTNode node) {
return !traverseNode(node);
}
protected final boolean visitNode(ASTNode node) {
return traverseNode(node);
}*/
//---- Hooks to create Flow info objects. User may introduce their own infos.
protected ReturnFlowInfo createReturn(ReturnStatement statement) {
return new ReturnFlowInfo(statement);
}
protected ThrowFlowInfo createThrow() {
return new ThrowFlowInfo();
}
protected BranchFlowInfo createBranch(Label label) {
return new BranchFlowInfo(label, fFlowContext);
}
protected GenericSequentialFlowInfo createSequential() {
return new GenericSequentialFlowInfo();
}
protected GenericConditionalFlowInfo createGenericConditional() {
return new GenericConditionalFlowInfo();
}
protected ConditionalFlowInfo createConditional() {
return new ConditionalFlowInfo();
}
protected ForInFlowInfo createForIn() {
return new ForInFlowInfo();
}
protected ForFlowInfo createFor() {
return new ForFlowInfo();
}
protected TryFlowInfo createTry() {
return new TryFlowInfo();
}
protected WhileFlowInfo createWhile() {
return new WhileFlowInfo();
}
protected IfFlowInfo createIf() {
return new IfFlowInfo();
}
protected DoWhileFlowInfo createDoWhile() {
return new DoWhileFlowInfo();
}
protected SwitchFlowInfo createSwitch() {
return new SwitchFlowInfo();
}
protected BlockFlowInfo createBlock() {
return new BlockFlowInfo();
}
protected MessageSendFlowInfo createMessageSendFlowInfo() {
return new MessageSendFlowInfo();
}
protected FlowContext getFlowContext() {
return fFlowContext;
}
//---- Helpers to access flow analysis objects ----------------------------------------
protected FlowInfo getFlowInfo(Node node) {
return fData.remove(node);
}
protected void setFlowInfo(Node node, FlowInfo info) {
fData.put(node, info);
}
protected FlowInfo assignFlowInfo(Node target, Node source) {
FlowInfo result= getFlowInfo(source);
setFlowInfo(target, result);
return result;
}
protected FlowInfo accessFlowInfo(Node node) {
return (FlowInfo)fData.get(node);
}
//---- Helpers to process sequential flow infos -------------------------------------
protected GenericSequentialFlowInfo processSequential(Node parent, List<? extends Node> nodes) {
GenericSequentialFlowInfo result= createSequential(parent);
process(result, nodes);
return result;
}
protected GenericSequentialFlowInfo processSequential(Node parent, Node node1) {
GenericSequentialFlowInfo result= createSequential(parent);
if (node1 != null)
result.merge(getFlowInfo(node1), fFlowContext);
return result;
}
protected GenericSequentialFlowInfo processSequential(Node parent, Node node1, Node node2) {
GenericSequentialFlowInfo result= createSequential(parent);
if (node1 != null)
result.merge(getFlowInfo(node1), fFlowContext);
if (node2 != null)
result.merge(getFlowInfo(node2), fFlowContext);
return result;
}
protected GenericSequentialFlowInfo createSequential(Node parent) {
GenericSequentialFlowInfo result= createSequential();
setFlowInfo(parent, result);
return result;
}
/*protected GenericSequentialFlowInfo createSequential(List nodes) {
GenericSequentialFlowInfo result= createSequential();
process(result, nodes);
return result;
}*/
//---- Generic merge methods --------------------------------------------------------
protected void process(GenericSequentialFlowInfo info, List<? extends Node> nodes) {
if (nodes == null)
return;
for (Node node : nodes) {
info.merge(getFlowInfo(node), fFlowContext);
}
}
/*protected void process(GenericSequentialFlowInfo info, ASTNode node) {
if (node != null)
info.merge(getFlowInfo(node), fFlowContext);
}
protected void process(GenericSequentialFlowInfo info, ASTNode node1, ASTNode node2) {
if (node1 != null)
info.merge(getFlowInfo(node1), fFlowContext);
if (node2 != null)
info.merge(getFlowInfo(node2), fFlowContext);
}*/
//---- Helper to process switch statement ----------------------------------------
protected SwitchData createSwitchData(SwitchStatement node) {
SwitchData result= new SwitchData();
List<SwitchElement> elements= node.getElements();
if (elements.isEmpty())
return result;
int start= -1, end= -1;
GenericSequentialFlowInfo info= null;
for (SwitchElement switchCase : elements) {
if (switchCase instanceof DefaultClause) {
result.setHasDefaultCase();
}
if (info == null) {
info= createSequential();
start= switchCase.getBegin();
} else {
if (info.isReturn() || info.isPartialReturn() || info.branches()) {
result.add(new Region(start, end - start), info);
info= createSequential();
start= switchCase.getBegin();
}
}
for(Statement element : switchCase.getStatements())
info.merge(getFlowInfo(element), fFlowContext);
end= switchCase.getEnd();
}
result.add(new Region(start, end - start), info);
return result;
}
protected void caseSwitchStatement(SwitchStatement node, SwitchData data) {
SwitchFlowInfo switchFlowInfo= createSwitch();
setFlowInfo(node, switchFlowInfo);
switchFlowInfo.mergeTest(getFlowInfo(node.getSelector()), fFlowContext);
FlowInfo[] cases= data.getInfos();
for (int i= 0; i < cases.length; i++)
switchFlowInfo.mergeCase(cases[i], fFlowContext);
switchFlowInfo.mergeDefault(data.hasDefaultCase(), fFlowContext);
switchFlowInfo.removeLabel(null);
}
//---- special visit methods -------------------------------------------------------
protected void traverse(Node node) {
if (isTraverseNeeded(node)) {
for(EObject obj : node.eContents())
traverse((Node)obj);
doSwitch(node);
}
}
public Boolean caseArrayAccessExpression(ArrayAccessExpression node) {
processSequential(node, node.getArray(), node.getIndex());
return true;
}
public Boolean caseArrayLiteral(ArrayLiteral node) {
//GenericSequentialFlowInfo info= processSequential(node, node.getType());
//process(info, node.dimensions());
//process(info, node.getInitializer());
processSequential(node, node.getElements());
return true;
}
@Override
public Boolean caseObjectLiteral(ObjectLiteral node) {
processSequential(node, node.getProperties());
return true;
}
public Boolean caseBlockStatement(BlockStatement node) {
GenericSequentialFlowInfo info= createBlock();
setFlowInfo(node, info);
process(info, node.getStatements());
return true;
}
public Boolean caseBreakStatement(BreakStatement node) {
setFlowInfo(node, createBranch(node.getLabel()));
return true;
}
public Boolean caseCatchClause(CatchClause node) {
processSequential(node, node.getException(), node.getBody());
return true;
}
public Boolean caseNewExpression(NewExpression node) {
GenericSequentialFlowInfo info= processSequential(node, node.getConstructor());
process(info, node.getArguments());
return true;
}
public Boolean caseSource(Source node) {
processSequential(node, node.getStatements());
return true;
}
public Boolean caseConditionalExpression(ConditionalExpression node) {
ConditionalFlowInfo info= createConditional();
setFlowInfo(node, info);
info.mergeCondition(getFlowInfo(node.getPredicate()), fFlowContext);
info.merge(
getFlowInfo(node.getConsequent()),
getFlowInfo(node.getAlternative()),
fFlowContext);
return true;
}
public Boolean caseContinueStatement(ContinueStatement node) {
setFlowInfo(node, createBranch(node.getLabel()));
return true;
}
public Boolean caseDoStatement(DoStatement node) {
DoWhileFlowInfo info= createDoWhile();
setFlowInfo(node, info);
info.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext);
info.removeLabel(null);
return true;
}
public Boolean caseForInStatement(ForInStatement node) {
ForInFlowInfo forInfo= createForIn();
setFlowInfo(node, forInfo);
forInfo.mergeParameter(getFlowInfo(node.getItem()), fFlowContext);
forInfo.mergeExpression(getFlowInfo(node.getCollection()), fFlowContext);
forInfo.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
forInfo.removeLabel(null);
return true;
}
public Boolean caseForEachInStatement(ForEachInStatement node) {
ForInFlowInfo forInfo= createForIn();
setFlowInfo(node, forInfo);
forInfo.mergeParameter(getFlowInfo(node.getItem()), fFlowContext);
forInfo.mergeExpression(getFlowInfo(node.getCollection()), fFlowContext);
forInfo.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
forInfo.removeLabel(null);
return true;
}
public Boolean caseExpressionStatement(ExpressionStatement node) {
assignFlowInfo(node, node.getExpression());
return true;
}
public Boolean casePropertyAccessExpression(PropertyAccessExpression node) {
processSequential(node, node.getObject(), node.getProperty());
return true;
}
public Boolean caseSimplePropertyAssignment(SimplePropertyAssignment node) {
processSequential(node, node.getInitializer());
return true;
}
public Boolean caseForStatement(ForStatement node) {
ForFlowInfo forInfo= createFor();
setFlowInfo(node, forInfo);
forInfo.mergeInitializer(getFlowInfo(node.getInitialization()), fFlowContext);
forInfo.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext);
forInfo.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
// Increments are executed after the action.
forInfo.mergeIncrement(createSequential(node.getIncrement()), fFlowContext);
forInfo.removeLabel(null);
return true;
}
public Boolean caseIfStatement(IfStatement node) {
IfFlowInfo info= createIf();
setFlowInfo(node, info);
info.mergeCondition(getFlowInfo(node.getPredicate()), fFlowContext);
info.merge(getFlowInfo(node.getConsequent()), getFlowInfo(node.getAlternative()), fFlowContext);
return true;
}
public Boolean caseBinaryExpression(BinaryExpression node) {
if (RefactoringUtils.isAssignment(node.getOperation())) {
FlowInfo lhs= getFlowInfo(node.getLeft());
FlowInfo rhs= getFlowInfo(node.getRight());
if (lhs instanceof LocalFlowInfo) {
LocalFlowInfo llhs= (LocalFlowInfo)lhs;
llhs.setWriteAccess(fFlowContext);
if (node.getOperation() != BinaryOperator.ASSIGN) {
GenericSequentialFlowInfo tmp= createSequential();
tmp.merge(new LocalFlowInfo(llhs, FlowInfo.READ, fFlowContext), fFlowContext);
tmp.merge(rhs, fFlowContext);
rhs= tmp;
}
}
GenericSequentialFlowInfo info= createSequential(node);
// first process right and side and then left hand side.
info.merge(rhs, fFlowContext);
info.merge(lhs, fFlowContext);
} else
processSequential(node, node.getLeft(), node.getRight());
return true;
}
public Boolean caseLabeledStatement(LabeledStatement node) {
FlowInfo info= assignFlowInfo(node, node.getStatement());
if (info != null)
info.removeLabel(node.getLabel());
return true;
}
public Boolean caseFunctionExpression(FunctionExpression node) {
GenericSequentialFlowInfo info = createSequential();
GenericSequentialFlowInfo inner = processSequential(node, node.getParameters());
inner.merge(getFlowInfo(node.getBody()), fFlowContext);
inner.setNoReturn();
callInfo.merge(inner, fFlowContext);
setFlowInfo(node,info);
return true;
}
public Boolean caseGetterAssignment(GetterAssignment node) {
GenericSequentialFlowInfo info= processSequential(node, node.getBody());
info.setNoReturn();
return true;
}
public Boolean caseSetterAssignment(SetterAssignment node) {
GenericSequentialFlowInfo info= processSequential(node, node.getParameter());
info.merge(getFlowInfo(node.getBody()), fFlowContext);
info.setNoReturn();
return true;
}
public Boolean caseCallExpression(CallExpression node) {
//getMethodBinding(node.getName())
MessageSendFlowInfo info= createMessageSendFlowInfo();
setFlowInfo(node, info);
for (Expression arg : node.getArguments()) {
info.mergeArgument(getFlowInfo(arg), fFlowContext);
}
info.mergeReceiver(getFlowInfo(node.getApplicant()), fFlowContext);
closureInfo.assignAccessMode(callInfo);
//info.mergeExceptions(binding, fFlowContext);
return true;
}
public Boolean caseParenthesizedExpression(ParenthesizedExpression node) {
assignFlowInfo(node, node.getEnclosed());
return true;
}
public Boolean caseUnaryExpression(UnaryExpression node) {
if (RefactoringUtils.hasSideEffect(node.getOperation())) {
FlowInfo info= getFlowInfo(node.getArgument());
if (info instanceof LocalFlowInfo) {
// Normally we should do this in the parent node since the write access take place later.
// But I couldn't come up with a case where this influences the flow analysis. So I kept
// it here to simplify the code.
GenericSequentialFlowInfo result= createSequential(node);
result.merge(info, fFlowContext);
result.merge(
new LocalFlowInfo((LocalFlowInfo)info, FlowInfo.WRITE, fFlowContext),
fFlowContext);
} else {
setFlowInfo(node, info);
}
} else {
assignFlowInfo(node, node.getArgument());
}
return true;
}
public Boolean caseReturnStatement(ReturnStatement node) {
if (createReturnFlowInfo(node)) {
ReturnFlowInfo info= createReturn(node);
setFlowInfo(node, info);
info.merge(getFlowInfo(node.getExpression()), fFlowContext);
} else {
assignFlowInfo(node, node.getExpression());
}
return true;
}
public Boolean caseVariableReference(VariableReference node) {
VariableBinding binding = fFlowContext.resolve(node.getVariable());
if (binding != null) {
setFlowInfo(node, new LocalFlowInfo(
binding,
FlowInfo.READ,
fFlowContext));
}
return true;
}
public Boolean caseSwitchStatement(SwitchStatement node) {
caseSwitchStatement(node, createSwitchData(node));
return true;
}
public Boolean caseThrowStatement(ThrowStatement node) {
ThrowFlowInfo info= createThrow();
setFlowInfo(node, info);
Expression expression= node.getException();
info.merge(getFlowInfo(expression), fFlowContext);
//info.mergeException(expression.resolveTypeBinding(), fFlowContext);
return true;
}
public Boolean caseTryStatement(TryStatement node) {
TryFlowInfo info= createTry();
setFlowInfo(node, info);
info.mergeTry(getFlowInfo(node.getBody()), fFlowContext);
//info.removeExceptions(node);
for (CatchClause element : node.getCatches()) {
info.mergeCatch(getFlowInfo(element), fFlowContext);
}
info.mergeFinally(getFlowInfo(node.getFinallyClause()), fFlowContext);
return true;
}
public Boolean caseVariableStatement(VariableStatement node) {
processSequential(node, node.getDeclarations());
return true;
}
public Boolean caseConstStatement(ConstStatement node) {
processSequential(node, node.getDeclarations());
return true;
}
public Boolean caseVariableDeclaration(VariableDeclaration node) {
VariableBinding binding= fFlowContext.resolve(node.getIdentifier());
LocalFlowInfo nameInfo= null;
Expression initializer= node.getInitializer();
if (binding != null && initializer != null) {
nameInfo= new LocalFlowInfo(binding, FlowInfo.WRITE, fFlowContext);
}
GenericSequentialFlowInfo info= processSequential(node, initializer);
info.merge(nameInfo, fFlowContext);
return true;
}
public Boolean caseWhileStatement(WhileStatement node) {
WhileFlowInfo info= createWhile();
setFlowInfo(node, info);
info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext);
info.mergeAction(getFlowInfo(node.getBody()), fFlowContext);
info.removeLabel(null);
return true;
}
/*private IMethodBinding getMethodBinding(Name name) {
if (name == null)
return null;
IBinding binding= name.resolveBinding();
if (binding instanceof IMethodBinding)
return (IMethodBinding)binding;
return null;
}*/
}