/*******************************************************************************
* 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.*;
import org.eclipse.php.core.ast.nodes.ITypeBinding;
import org.eclipse.php.core.ast.nodes.IVariableBinding;
import org.eclipse.php.core.ast.nodes.Identifier;
import org.eclipse.php.core.ast.nodes.TryStatement;
public abstract class FlowInfo {
// Return statement handling.
protected static final int NOT_POSSIBLE = 0;
protected static final int UNDEFINED = 1;
protected static final int NO_RETURN = 2;
protected static final int PARTIAL_RETURN = 3;
protected static final int VOID_RETURN = 4;
protected static final int VALUE_RETURN = 5;
protected static final int THROW = 6;
// Local access handling.
public static final int UNUSED = 1 << 0;
public static final int READ = 1 << 1;
public static final int READ_POTENTIAL = 1 << 2;
public static final int WRITE = 1 << 3;
public static final int WRITE_POTENTIAL = 1 << 4;
public static final int UNKNOWN = 1 << 5;
// Table to merge access modes for condition statements (e.g branch[x] ||
// branch[y]).
private static final int[][] ACCESS_MODE_CONDITIONAL_TABLE = {
/* UNUSED READ READ_POTENTIAL WRTIE WRITE_POTENTIAL UNKNOWN */
/* UNUSED */{ UNUSED, READ_POTENTIAL, READ_POTENTIAL,
WRITE_POTENTIAL, WRITE_POTENTIAL, UNKNOWN },
/* READ */{ READ_POTENTIAL, READ, READ_POTENTIAL, UNKNOWN, UNKNOWN,
UNKNOWN },
/* READ_POTENTIAL */{ READ_POTENTIAL, READ_POTENTIAL,
READ_POTENTIAL, UNKNOWN, UNKNOWN, UNKNOWN },
/* WRITE */{ WRITE_POTENTIAL, UNKNOWN, UNKNOWN, WRITE,
WRITE_POTENTIAL, UNKNOWN },
/* WRITE_POTENTIAL */{ WRITE_POTENTIAL, UNKNOWN, UNKNOWN,
WRITE_POTENTIAL, WRITE_POTENTIAL, UNKNOWN },
/* UNKNOWN */{ UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN } };
// Table to change access mode if there is an open branch statement
private static final int[] ACCESS_MODE_OPEN_BRANCH_TABLE = {
/* UNUSED READ READ_POTENTIAL WRTIE WRITE_POTENTIAL UNKNOWN */
UNUSED, READ_POTENTIAL, READ_POTENTIAL, WRITE_POTENTIAL, WRITE_POTENTIAL,
UNKNOWN };
// Table to merge return modes for condition statements (y: fReturnKind, x:
// other.fReturnKind)
private static final int[][] RETURN_KIND_CONDITIONAL_TABLE = {
/*
* NOT_POSSIBLE UNDEFINED NO_RETURN PARTIAL_RETURN VOID_RETURN
* VALUE_RETURN THROW
*/
/* NOT_POSSIBLE */{ NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE,
NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE },
/* UNDEFINED */{ NOT_POSSIBLE, UNDEFINED, NO_RETURN,
PARTIAL_RETURN, VOID_RETURN, VALUE_RETURN, THROW },
/* NO_RETURN */{ NOT_POSSIBLE, NO_RETURN, NO_RETURN,
PARTIAL_RETURN, PARTIAL_RETURN, PARTIAL_RETURN, NO_RETURN },
/* PARTIAL_RETURN */{ NOT_POSSIBLE, PARTIAL_RETURN, PARTIAL_RETURN,
PARTIAL_RETURN, PARTIAL_RETURN, PARTIAL_RETURN,
PARTIAL_RETURN },
/* VOID_RETURN */{ NOT_POSSIBLE, VOID_RETURN, PARTIAL_RETURN,
PARTIAL_RETURN, VOID_RETURN, NOT_POSSIBLE, VOID_RETURN },
/* VALUE_RETURN */{ NOT_POSSIBLE, VALUE_RETURN, PARTIAL_RETURN,
PARTIAL_RETURN, NOT_POSSIBLE, VALUE_RETURN, VALUE_RETURN },
/* THROW */{ NOT_POSSIBLE, THROW, NO_RETURN, PARTIAL_RETURN,
VOID_RETURN, VALUE_RETURN, THROW } };
// Table to merge return modes for sequential statements (y: fReturnKind, x:
// other.fReturnKind)
private static final int[][] RETURN_KIND_SEQUENTIAL_TABLE = {
/*
* NOT_POSSIBLE UNDEFINED NO_RETURN PARTIAL_RETURN VOID_RETURN
* VALUE_RETURN THROW
*/
/* NOT_POSSIBLE */{ NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE,
NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE, NOT_POSSIBLE },
/* UNDEFINED */{ NOT_POSSIBLE, UNDEFINED, NO_RETURN,
PARTIAL_RETURN, VOID_RETURN, VALUE_RETURN, THROW },
/* NO_RETURN */{ NOT_POSSIBLE, NO_RETURN, NO_RETURN,
PARTIAL_RETURN, VOID_RETURN, VALUE_RETURN, THROW },
/* PARTIAL_RETURN */{ NOT_POSSIBLE, PARTIAL_RETURN, PARTIAL_RETURN,
PARTIAL_RETURN, VOID_RETURN, VALUE_RETURN, THROW },
/* VOID_RETURN */{ NOT_POSSIBLE, VOID_RETURN, VOID_RETURN,
PARTIAL_RETURN, VOID_RETURN, NOT_POSSIBLE, NOT_POSSIBLE },
/* VALUE_RETURN */{ NOT_POSSIBLE, VALUE_RETURN, VALUE_RETURN,
PARTIAL_RETURN, NOT_POSSIBLE, VALUE_RETURN, NOT_POSSIBLE },
/* THROW */{ NOT_POSSIBLE, THROW, THROW, PARTIAL_RETURN,
VOID_RETURN, VALUE_RETURN, THROW } };
protected static final String UNLABELED = "@unlabeled"; //$NON-NLS-1$
protected static final IVariableBinding[] EMPTY_ARRAY = new IVariableBinding[0];
protected int fReturnKind;
protected int[] fAccessModes;
protected Set fBranches;
protected Set fExceptions;
protected Set fTypeVariables;
protected FlowInfo() {
this(UNDEFINED);
}
protected FlowInfo(int returnKind) {
fReturnKind = returnKind;
}
// ---- General Helpers
// ----------------------------------------------------------
protected void assignExecutionFlow(FlowInfo right) {
fReturnKind = right.fReturnKind;
fBranches = right.fBranches;
fExceptions = right.fExceptions;
}
protected void assignAccessMode(FlowInfo right) {
fAccessModes = right.fAccessModes;
}
protected void assign(FlowInfo right) {
assignExecutionFlow(right);
assignAccessMode(right);
}
protected void mergeConditional(FlowInfo info, FlowContext context) {
mergeAccessModeConditional(info, context);
mergeExecutionFlowConditional(info, context);
mergeTypeVariablesConditional(info, context);
}
protected void mergeSequential(FlowInfo info, FlowContext context) {
mergeAccessModeSequential(info, context);
mergeExecutionFlowSequential(info, context);
mergeTypeVariablesSequential(info, context);
}
// ---- Return Kind
// ------------------------------------------------------------------
public void setNoReturn() {
fReturnKind = NO_RETURN;
}
public boolean isUndefined() {
return fReturnKind == UNDEFINED;
}
public boolean isNoReturn() {
return fReturnKind == NO_RETURN;
}
public boolean isPartialReturn() {
return fReturnKind == PARTIAL_RETURN;
}
public boolean isVoidReturn() {
return fReturnKind == VOID_RETURN;
}
public boolean isValueReturn() {
return fReturnKind == VALUE_RETURN;
}
public boolean isThrow() {
return fReturnKind == THROW;
}
public boolean isReturn() {
return fReturnKind == VOID_RETURN || fReturnKind == VALUE_RETURN;
}
// ---- Branches
// -------------------------------------------------------------------------
public boolean branches() {
return fBranches != null && !fBranches.isEmpty();
}
protected Set getBranches() {
return fBranches;
}
protected void removeLabel(Identifier label) {
if (fBranches != null) {
fBranches.remove(makeString(label));
if (fBranches.isEmpty())
fBranches = null;
}
}
protected static String makeString(Identifier label) {
if (label == null)
return UNLABELED;
else
return label.getName();
}
// ---- Exceptions
// -----------------------------------------------------------------------
public ITypeBinding[] getExceptions() {
if (fExceptions == null)
return new ITypeBinding[0];
return (ITypeBinding[]) fExceptions
.toArray(new ITypeBinding[fExceptions.size()]);
}
protected boolean hasUncaughtException() {
return fExceptions != null && !fExceptions.isEmpty();
}
protected void addException(ITypeBinding type) {
if (fExceptions == null)
fExceptions = new HashSet(2);
fExceptions.add(type);
}
protected void removeExceptions(TryStatement node) {
if (fExceptions == null)
return;
List catchClauses = node.catchClauses();
if (catchClauses.isEmpty())
return;
// Make sure we have a copy since we are modifing the fExceptions list
ITypeBinding[] exceptions = (ITypeBinding[]) fExceptions
.toArray(new ITypeBinding[fExceptions.size()]);
for (int i = 0; i < exceptions.length; i++) {
handleException(catchClauses, exceptions[i]);
}
if (fExceptions.isEmpty())
fExceptions = null;
}
private void handleException(List catchClauses, ITypeBinding type) {
// TODO - implement with the new model
// for (Iterator iter= catchClauses.iterator(); iter.hasNext();) {
// Identifier binding= ((CatchClause)iter.next()).getClassName();
// if (binding == null)
// continue;
// while (catchedType != null) {
// if (catchedType == type) {
// fExceptions.remove(type);
// return;
// }
// catchedType= catchedType.getSuperclass();
// }
// }
}
// ---- Type parameters
// -----------------------------------------------------------------
public ITypeBinding[] getTypeVariables() {
if (fTypeVariables == null)
return new ITypeBinding[0];
return (ITypeBinding[]) fTypeVariables
.toArray(new ITypeBinding[fTypeVariables.size()]);
}
protected void addTypeVariable(ITypeBinding typeParameter) {
if (fTypeVariables == null)
fTypeVariables = new HashSet();
fTypeVariables.add(typeParameter);
}
private void mergeTypeVariablesSequential(FlowInfo otherInfo,
FlowContext context) {
fTypeVariables = mergeSets(fTypeVariables, otherInfo.fTypeVariables);
}
private void mergeTypeVariablesConditional(FlowInfo otherInfo,
FlowContext context) {
fTypeVariables = mergeSets(fTypeVariables, otherInfo.fTypeVariables);
}
// ---- Execution flow
// -------------------------------------------------------------------
private void mergeExecutionFlowSequential(FlowInfo otherInfo,
FlowContext context) {
int other = otherInfo.fReturnKind;
if (branches() && other == VALUE_RETURN)
other = PARTIAL_RETURN;
fReturnKind = RETURN_KIND_SEQUENTIAL_TABLE[fReturnKind][other];
mergeBranches(otherInfo, context);
mergeExceptions(otherInfo, context);
}
private void mergeExecutionFlowConditional(FlowInfo otherInfo,
FlowContext context) {
fReturnKind = RETURN_KIND_CONDITIONAL_TABLE[fReturnKind][otherInfo.fReturnKind];
mergeBranches(otherInfo, context);
mergeExceptions(otherInfo, context);
}
private void mergeBranches(FlowInfo otherInfo, FlowContext context) {
fBranches = mergeSets(fBranches, otherInfo.fBranches);
}
private void mergeExceptions(FlowInfo otherInfo, FlowContext context) {
fExceptions = mergeSets(fExceptions, otherInfo.fExceptions);
}
private static Set mergeSets(Set thisSet, Set otherSet) {
if (otherSet != null) {
if (thisSet == null) {
thisSet = otherSet;
} else {
Iterator iter = otherSet.iterator();
while (iter.hasNext()) {
thisSet.add(iter.next());
}
}
}
return thisSet;
}
// ---- Local access handling
// --------------------------------------------------
/**
* Returns an array of <code>IVariableBinding</code> that conform to the
* given access mode <code>mode</code>.
*
* @param context
* the flow context object used to compute this flow info
* @param mode
* the access type. Valid values are <code>READ</code>,
* <code>WRITE</code>, <code>UNKNOWN</code> and any combination
* of them.
* @return an array of local variable bindings conforming to the given type.
*/
public IVariableBinding[] get(FlowContext context, int mode) {
List result = new ArrayList();
int[] locals = getAccessModes();
if (locals == null)
return EMPTY_ARRAY;
for (int i = 0; i < locals.length; i++) {
int accessMode = locals[i];
if ((accessMode & mode) != 0)
result.add(context.getLocalFromIndex(i));
}
return (IVariableBinding[]) result.toArray(new IVariableBinding[result
.size()]);
}
/**
* Checks whether the given local variable binding has the given access
* mode.
*
* @return <code>true</code> if the binding has the given access mode.
* <code>False</code> otherwise
*/
public boolean hasAccessMode(FlowContext context, IVariableBinding local,
int mode) {
boolean unusedMode = (mode & UNUSED) != 0;
if (fAccessModes == null && unusedMode)
return true;
int index = context.getIndexFromLocal(local);
if (index == -1)
return unusedMode;
return (fAccessModes[index] & mode) != 0;
}
/**
* Returns the access mode of the local variable identified by the given
* binding.
*
* @param context
* the flow context used during flow analysis
* @param local
* the local variable of interest
* @return the access mode of the local variable
*/
public int getAccessMode(FlowContext context, IVariableBinding local) {
if (fAccessModes == null)
return UNUSED;
int index = context.getIndexFromLocal(local);
if (index == -1)
return UNUSED;
return fAccessModes[index];
}
protected int[] getAccessModes() {
return fAccessModes;
}
protected void clearAccessMode(IVariableBinding binding, FlowContext context) {
if (fAccessModes == null) // all are unused
return;
fAccessModes[binding.getVariableId() - context.getStartingIndex()] = UNUSED;
}
protected void mergeAccessModeSequential(FlowInfo otherInfo,
FlowContext context) {
if (!context.considerAccessMode())
return;
int[] others = otherInfo.fAccessModes;
if (others == null) // others are all unused. So nothing to do
return;
// Must not consider return kind since a return statement can't control
// execution flow
// inside a method. It always leaves the method.
if (branches() || hasUncaughtException()) {
for (int i = 0; i < others.length; i++)
others[i] = ACCESS_MODE_OPEN_BRANCH_TABLE[getIndex(others[i])];
}
if (fAccessModes == null) { // all current variables are unused
fAccessModes = others;
return;
}
if (context.computeArguments()) {
handleComputeArguments(others);
} else if (context.computeReturnValues()) {
handleComputeReturnValues(others);
} else if (context.computeMerge()) {
handleMergeValues(others);
}
}
private void handleComputeReturnValues(int[] others) {
for (int i = 0; i < fAccessModes.length; i++) {
int accessmode = fAccessModes[i];
int othermode = others[i];
if (accessmode == WRITE)
continue;
if (accessmode == WRITE_POTENTIAL) {
if (othermode == WRITE)
fAccessModes[i] = WRITE;
continue;
}
if (others[i] != UNUSED)
fAccessModes[i] = othermode;
}
}
private void handleComputeArguments(int[] others) {
for (int i = 0; i < fAccessModes.length; i++) {
int accessMode = fAccessModes[i];
int otherMode = others[i];
if (accessMode == UNUSED) {
fAccessModes[i] = otherMode;
} else if (accessMode == WRITE_POTENTIAL
&& (otherMode == READ || otherMode == READ_POTENTIAL)) {
// Read always supersedes a potential write even if the read is
// potential as well
// (we have to consider the potential read as an argument then).
fAccessModes[i] = otherMode;
} else if (accessMode == WRITE_POTENTIAL && otherMode == WRITE) {
fAccessModes[i] = WRITE;
}
}
}
private void handleMergeValues(int[] others) {
for (int i = 0; i < fAccessModes.length; i++) {
fAccessModes[i] = ACCESS_MODE_CONDITIONAL_TABLE[getIndex(fAccessModes[i])][getIndex(others[i])];
}
}
protected void createAccessModeArray(FlowContext context) {
fAccessModes = new int[context.getArrayLength()];
for (int i = 0; i < fAccessModes.length; i++) {
fAccessModes[i] = UNUSED;
}
}
protected void mergeAccessModeConditional(FlowInfo otherInfo,
FlowContext context) {
if (!context.considerAccessMode())
return;
int[] others = otherInfo.fAccessModes;
// first access
if (fAccessModes == null) {
if (others != null)
fAccessModes = others;
else
createAccessModeArray(context);
return;
} else {
if (others == null) {
for (int i = 0; i < fAccessModes.length; i++) {
int unused_index = getIndex(UNUSED);
fAccessModes[i] = ACCESS_MODE_CONDITIONAL_TABLE[getIndex(fAccessModes[i])][unused_index];
}
} else {
for (int i = 0; i < fAccessModes.length; i++) {
fAccessModes[i] = ACCESS_MODE_CONDITIONAL_TABLE[getIndex(fAccessModes[i])][getIndex(others[i])];
}
}
}
}
protected void mergeEmptyCondition(FlowContext context) {
if (fReturnKind == VALUE_RETURN || fReturnKind == VOID_RETURN)
fReturnKind = PARTIAL_RETURN;
if (!context.considerAccessMode())
return;
if (fAccessModes == null) {
createAccessModeArray(context);
return;
}
int unused_index = getIndex(UNUSED);
for (int i = 0; i < fAccessModes.length; i++) {
fAccessModes[i] = ACCESS_MODE_CONDITIONAL_TABLE[getIndex(fAccessModes[i])][unused_index];
}
}
private static int getIndex(int accessMode) {
// Fast log function
switch (accessMode) {
case UNUSED:
return 0;
case READ:
return 1;
case READ_POTENTIAL:
return 2;
case WRITE:
return 3;
case WRITE_POTENTIAL:
return 4;
case UNKNOWN:
return 5;
}
return -1;
}
}