/******************************************************************************* * Copyright (c) 2000, 2015 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 * Zend Technologies - adapt for PHP refactoring *******************************************************************************/ package org.eclipse.php.refactoring.core.code.flow; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.php.core.ast.nodes.*; import org.eclipse.php.core.ast.visitor.ApplyAll; /** * 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 ApplyAll { 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 HashMap<ASTNode, FlowInfo> fData = new HashMap<ASTNode, FlowInfo>(100); /* package */FlowContext fFlowContext = null; public FlowAnalyzer(FlowContext context) { fFlowContext = context; } protected abstract boolean createReturnFlowInfo(ReturnStatement node); protected abstract boolean traverseNode(ASTNode node); protected boolean skipNode(ASTNode node) { return !traverseNode(node); } protected final boolean apply(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(Identifier label) { return new BranchFlowInfo(label, fFlowContext); } protected GenericSequentialFlowInfo createSequential() { return new GenericSequentialFlowInfo(); } protected ConditionalFlowInfo createConditional() { return new ConditionalFlowInfo(); } protected EnhancedForFlowInfo createEnhancedFor() { return new EnhancedForFlowInfo(); } 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(ASTNode node) { return (FlowInfo) fData.remove(node); } protected void setFlowInfo(ASTNode node, FlowInfo info) { fData.put(node, info); } protected FlowInfo assignFlowInfo(ASTNode target, ASTNode source) { FlowInfo result = getFlowInfo(source); setFlowInfo(target, result); return result; } protected FlowInfo accessFlowInfo(ASTNode node) { return (FlowInfo) fData.get(node); } // ---- Helpers to process sequential flow infos // ------------------------------------- protected GenericSequentialFlowInfo processSequential(ASTNode parent, List<? extends ASTNode> nodes) { GenericSequentialFlowInfo result = createSequential(parent); process(result, nodes); return result; } protected GenericSequentialFlowInfo processSequential(ASTNode parent, ASTNode node1) { GenericSequentialFlowInfo result = createSequential(parent); if (node1 != null) result.merge(getFlowInfo(node1), fFlowContext); return result; } protected GenericSequentialFlowInfo processSequential(ASTNode parent, ASTNode node1, ASTNode 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(ASTNode parent) { GenericSequentialFlowInfo result = createSequential(); setFlowInfo(parent, result); return result; } protected GenericSequentialFlowInfo createSequential(List<? extends ASTNode> nodes) { GenericSequentialFlowInfo result = createSequential(); process(result, nodes); return result; } // ---- Generic merge methods // -------------------------------------------------------- protected void process(GenericSequentialFlowInfo info, List<? extends ASTNode> nodes) { if (nodes == null) return; for (Iterator<? extends ASTNode> iter = nodes.iterator(); iter.hasNext();) { info.merge(getFlowInfo(iter.next()), 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); } // ---- special visit methods // ------------------------------------------------------- public boolean visit(EmptyStatement node) { // Empty statements aren't of any interest. return false; } public boolean visit(TryStatement node) { if (traverseNode(node)) { fFlowContext.pushExcptions(node); node.getBody().accept(this); fFlowContext.popExceptions(); List<CatchClause> catchClauses = node.catchClauses(); for (CatchClause catchClause : catchClauses) { catchClause.accept(this); } } return false; } // ---- Helper to process switch statement // ---------------------------------------- protected SwitchData createSwitchData(SwitchStatement node) { SwitchData result = new SwitchData(); List<Statement> statements = node.getBody().statements(); if (statements.isEmpty()) return result; int start = -1, end = -1; GenericSequentialFlowInfo info = null; for (Iterator<Statement> iter = statements.iterator(); iter.hasNext();) { Statement statement = (Statement) iter.next(); if (statement instanceof SwitchCase) { SwitchCase switchCase = (SwitchCase) statement; if (switchCase.isDefault()) { result.setHasDefaultCase(); } if (info == null) { info = createSequential(); start = statement.getStart(); } else { if (info.isReturn() || info.isPartialReturn() || info.branches()) { result.add(new Region(start, end - start + 1), info); info = createSequential(); start = statement.getStart(); } } } else if (info != null) { info.merge(getFlowInfo(statement), fFlowContext); } end = statement.getEnd() - 1; } result.add(new Region(start, end - start + 1), info); return result; } // ---- concrete endVisit methods // --------------------------------------------------- // TODO - when should we call "info.setNoReturn();" ? public void endVisit(ArrayAccess node) { if (skipNode(node)) return; processSequential(node, node.getName(), node.getIndex()); } public void endVisit(ArrayCreation node) { if (skipNode(node)) return; processSequential(node, node.elements()); } public void endVisit(ArrayElement node) { if (skipNode(node)) return; processSequential(node, node.getKey(), node.getValue()); } public void endVisit(Assignment node) { if (skipNode(node)) return; FlowInfo lhs = getFlowInfo(node.getLeftHandSide()); FlowInfo rhs = getFlowInfo(node.getRightHandSide()); if (lhs instanceof LocalFlowInfo) { LocalFlowInfo llhs = (LocalFlowInfo) lhs; llhs.setWriteAccess(fFlowContext); if (node.getOperator() != Assignment.OP_EQUAL) { 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); } public void endVisit(BackTickExpression node) { if (skipNode(node)) return; processSequential(node, node.expressions()); } public void endVisit(Block node) { if (skipNode(node)) return; BlockFlowInfo info = createBlock(); setFlowInfo(node, info); process(info, node.statements()); } public void endVisit(BreakStatement node) { if (skipNode(node)) return; // TODO - what about int value? // setFlowInfo(node, createBranch(node.getLabel())); processSequential(node, node.getExpression()); } public void endVisit(CastExpression node) { if (skipNode(node)) return; processSequential(node, node.getExpression()); } public void endVisit(CatchClause node) { if (skipNode(node)) return; processSequential(node, node.getVariable(), node.getBody()); } public void endVisit(ConstantDeclaration node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.names()); process(info, node.initializers()); } public void endVisit(ClassDeclaration node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getName()); process(info, node.getSuperClass()); process(info, node.interfaces()); process(info, node.getBody()); } public void endVisit(ClassInstanceCreation node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getClassName()); process(info, node.ctorParams()); } public void endVisit(ClassName node) { if (skipNode(node)) return; processSequential(node, node.getName()); } public void endVisit(CloneExpression node) { if (skipNode(node)) return; processSequential(node, node.getExpression()); } public void endVisit(Comment node) { // nothing to do } public void endVisit(ConditionalExpression node) { if (skipNode(node)) return; ConditionalFlowInfo info = createConditional(); setFlowInfo(node, info); info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext); info.merge(getFlowInfo(node.getIfTrue()), getFlowInfo(node.getIfFalse()), fFlowContext); } public void endVisit(ContinueStatement node) { if (skipNode(node)) return; // TODO - what about int value? // setFlowInfo(node, createBranch(node.getLabel())); processSequential(node, node.getExpression()); } public void endVisit(DeclareStatement node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.directiveNames()); process(info, node.directiveValues()); process(info, node.getBody()); } public void endVisit(DoStatement node) { if (skipNode(node)) return; DoWhileFlowInfo info = createDoWhile(); setFlowInfo(node, info); info.mergeAction(getFlowInfo(node.getBody()), fFlowContext); info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext); info.removeLabel(null); } public void endVisit(EchoStatement node) { if (skipNode(node)) return; processSequential(node, node.expressions()); } public void endVisit(EmptyStatement node) { // Leaf node. } public void endVisit(ExpressionStatement node) { if (skipNode(node)) return; assignFlowInfo(node, node.getExpression()); } public void endVisit(FieldAccess node) { if (skipNode(node)) return; processSequential(node, node.getField(), node.getField().getName()); } public void endVisit(FieldsDeclaration node) { if (skipNode(node)) return; processSequential(node, node.fields()); } // TODO - ensure the right order of the merge public void endVisit(ForEachStatement node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getExpression()); process(info, node.getKey()); process(info, node.getValue()); process(info, node.getStatement()); } public void endVisit(FormalParameter node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getParameterType()); process(info, node.getParameterName()); process(info, node.getDefaultValue()); } public void endVisit(ForStatement node) { if (skipNode(node)) return; ForFlowInfo forInfo = createFor(); setFlowInfo(node, forInfo); forInfo.mergeInitializer(createSequential(node.initializers()), fFlowContext); forInfo.mergeCondition(createSequential(node.conditions()), fFlowContext); forInfo.mergeAction(getFlowInfo(node.getBody()), fFlowContext); // Increments are executed after the action. forInfo.mergeIncrement(createSequential(node.updaters()), fFlowContext); forInfo.removeLabel(null); } public void endVisit(FunctionDeclaration node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.formalParameters()); process(info, node.getBody()); } public void endVisit(FunctionInvocation node) { endVisitFunctionInvocation(node, node.parameters(), getMethodBinding(node)); } public void endVisit(FunctionName node) { if (skipNode(node)) return; processSequential(node, node.getName()); } public void endVisit(GlobalStatement node) { if (skipNode(node)) return; processSequential(node, node.variables()); } public void endVisit(Identifier node) { } public void endVisit(IfStatement node) { if (skipNode(node)) return; IfFlowInfo info = createIf(); setFlowInfo(node, info); info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext); info.merge(getFlowInfo(node.getTrueStatement()), getFlowInfo(node.getFalseStatement()), fFlowContext); } public void endVisit(Include node) { if (skipNode(node)) return; processSequential(node, node.getExpression()); } public void endVisit(InfixExpression node) { if (skipNode(node)) return; processSequential(node, node.getLeft(), node.getRight()); } public void endVisit(InLineHtml node) { // nothing to do } public void endVisit(InterfaceDeclaration node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getName()); process(info, node.interfaces()); process(info, node.getBody()); } public void endVisit(InstanceOfExpression node) { if (skipNode(node)) return; processSequential(node, node.getExpression(), node.getClassName()); } public void endVisit(MethodDeclaration node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getFunction().formalParameters()); // process(info, node.parameters()); process(info, node.getFunction().getBody()); } public void endVisit(MethodInvocation node) { endVisitMethodInvocation(node, node.getDispatcher(), node.getMethod().parameters(), getMethodBinding(node.getMethod())); } public void endVisit(ParenthesisExpression node) { if (skipNode(node)) return; assignFlowInfo(node, node.getExpression()); } public void endVisit(PostfixExpression node) { endVisitIncDecOperation(node, node.getVariable()); } public void endVisit(PrefixExpression node) { endVisitIncDecOperation(node, node.getVariable()); } public void endVisit(Program node) { if (skipNode(node)) return; processSequential(node, node.statements()); } public void endVisit(Quote node) { if (skipNode(node)) return; processSequential(node, node.expressions()); } public void endVisit(Reference node) { if (skipNode(node)) return; processSequential(node, node.getExpression()); } public void endVisit(ReflectionVariable node) { if (skipNode(node)) return; processSequential(node, node.getName()); } public void endVisit(ReturnStatement node) { if (skipNode(node)) return; if (createReturnFlowInfo(node)) { ReturnFlowInfo info = createReturn(node); setFlowInfo(node, info); info.merge(getFlowInfo(node.getExpression()), fFlowContext); } else { assignFlowInfo(node, node.getExpression()); } } public void endVisit(Scalar node) { // nothing to do } public void endVisit(SingleFieldDeclaration node) { if (skipNode(node)) return; processSequential(node, node.getName()); } public void endVisit(StaticConstantAccess node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getClassName()); process(info, node.getConstant()); } public void endVisit(StaticFieldAccess node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getClassName()); process(info, node.getField()); } public void endVisit(StaticMethodInvocation node) { if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getClassName()); process(info, node.getMethod()); } public void endVisit(StaticStatement node) { if (skipNode(node)) return; processSequential(node, node.expressions()); } public void endVisit(SwitchCase node) { // TODO ??? // endVisitNode(node); if (skipNode(node)) return; GenericSequentialFlowInfo info = processSequential(node, node.getValue()); process(info, node.actions()); } public void endVisit(SwitchStatement node) { if (skipNode(node)) return; endVisit(node, createSwitchData(node)); } protected void endVisit(SwitchStatement node, SwitchData data) { SwitchFlowInfo switchFlowInfo = createSwitch(); setFlowInfo(node, switchFlowInfo); switchFlowInfo.mergeTest(getFlowInfo(node.getExpression()), 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); } public void endVisit(ThrowStatement node) { if (skipNode(node)) return; ThrowFlowInfo info = createThrow(); setFlowInfo(node, info); Expression expression = node.getExpression(); info.merge(getFlowInfo(expression), fFlowContext); info.mergeException(expression.resolveTypeBinding(), fFlowContext); } public void endVisit(TryStatement node) { if (skipNode(node)) return; TryFlowInfo info = createTry(); setFlowInfo(node, info); info.mergeTry(getFlowInfo(node.getBody()), fFlowContext); info.removeExceptions(node); List<CatchClause> catchClauses = node.catchClauses(); for (CatchClause catchClause : catchClauses) { info.mergeCatch(getFlowInfo(catchClause), fFlowContext); } } public void endVisit(UnaryOperation node) { assignFlowInfo(node, node.getExpression()); } public void endVisit(Variable node) { if (skipNode(node)) return; IVariableBinding binding = node.resolveVariableBinding(); if (binding != null && !binding.isField()) { setFlowInfo(node, new LocalFlowInfo(binding, FlowInfo.READ, fFlowContext)); } } public void endVisit(WhileStatement node) { if (skipNode(node)) return; WhileFlowInfo info = createWhile(); setFlowInfo(node, info); info.mergeCondition(getFlowInfo(node.getCondition()), fFlowContext); info.mergeAction(getFlowInfo(node.getBody()), fFlowContext); info.removeLabel(null); } // TODO - do we need this code? do we need the binding? private void endVisitMethodInvocation(ASTNode node, ASTNode receiver, List<Expression> arguments, IFunctionBinding binding) { if (skipNode(node)) return; MessageSendFlowInfo info = createMessageSendFlowInfo(); setFlowInfo(node, info); for (Iterator<Expression> iter = arguments.iterator(); iter.hasNext();) { Expression arg = iter.next(); info.mergeArgument(getFlowInfo(arg), fFlowContext); } info.mergeReceiver(getFlowInfo(receiver), fFlowContext); // info.mergeExceptions(binding, fFlowContext); } // TODO - do we need this code? do we need the binding? private void endVisitFunctionInvocation(ASTNode node, List<Expression> arguments, IFunctionBinding binding) { if (skipNode(node)) return; MessageSendFlowInfo info = createMessageSendFlowInfo(); setFlowInfo(node, info); for (Iterator<Expression> iter = arguments.iterator(); iter.hasNext();) { Expression arg = (Expression) iter.next(); info.mergeArgument(getFlowInfo(arg), fFlowContext); } // info.mergeExceptions(binding, fFlowContext); } private void endVisitIncDecOperation(Expression node, Expression operand) { if (skipNode(node)) return; FlowInfo info = getFlowInfo(operand); 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); } } private IFunctionBinding getMethodBinding(FunctionInvocation function) { // TODO - check what is the final purpose of calling this method if (function == null) return null; IBinding binding = function.resolveFunctionBinding(); if (binding instanceof IFunctionBinding) return (IMethodBinding) binding; return null; } }