restructure tabulation with multiple return sites.
delete some obsolete ExplodedSupergraph crud. git-svn-id: https://wala.svn.sourceforge.net/svnroot/wala/trunk@2773 f5eafffb-2e1d-0410-98e4-8ec43c5233c4
This commit is contained in:
parent
96952cd431
commit
27f886b7af
|
@ -1,259 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2002 - 2006 IBM Corporation.
|
||||
* 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 com.ibm.wala.dataflow.IFDS;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.ibm.wala.util.collections.HashSetFactory;
|
||||
import com.ibm.wala.util.debug.Assertions;
|
||||
import com.ibm.wala.util.debug.UnimplementedError;
|
||||
import com.ibm.wala.util.graph.Graph;
|
||||
import com.ibm.wala.util.intset.IntIterator;
|
||||
import com.ibm.wala.util.intset.IntSet;
|
||||
|
||||
/**
|
||||
*
|
||||
* A view of a supergraph as an exploded supergraph.
|
||||
*
|
||||
* Nodes are ExplodedSupergraphNodes. Edges are edges as realized by IFDS flow
|
||||
* functions;
|
||||
*
|
||||
* Note: not terribly efficient, use with care.
|
||||
*
|
||||
* @author sfink
|
||||
*/
|
||||
public class ExplodedSupergraph<T> implements Graph<ExplodedSupergraphNode<T>> {
|
||||
|
||||
private final ISupergraph<T,?> supergraph;
|
||||
|
||||
private final IFlowFunctionMap<T> flowFunctions;
|
||||
|
||||
public ExplodedSupergraph(ISupergraph<T,?> supergraph, IFlowFunctionMap<T> flowFunctions) {
|
||||
this.supergraph = supergraph;
|
||||
this.flowFunctions = flowFunctions;
|
||||
}
|
||||
|
||||
public void removeNodeAndEdges(ExplodedSupergraphNode N) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Iterator<ExplodedSupergraphNode<T>> iterator() throws UnimplementedError {
|
||||
Assertions.UNREACHABLE();
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getNumberOfNodes() throws UnimplementedError {
|
||||
Assertions.UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void addNode(ExplodedSupergraphNode n) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
}
|
||||
|
||||
public void removeNode(ExplodedSupergraphNode n) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean containsNode(ExplodedSupergraphNode N) throws UnimplementedError {
|
||||
Assertions.UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
public Iterator<ExplodedSupergraphNode<T>> getPredNodes(ExplodedSupergraphNode<T> node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
T dest = node.getSupergraphNode();
|
||||
HashSet<ExplodedSupergraphNode<T>> result = HashSetFactory.make(supergraph.getPredNodeCount(dest));
|
||||
for (Iterator<? extends T> it = supergraph.getPredNodes(dest); it.hasNext();) {
|
||||
T src = it.next();
|
||||
if (supergraph.classifyEdge(src, dest) != ISupergraph.RETURN_EDGE) {
|
||||
IFlowFunction f = getFlowFunction(src, dest);
|
||||
if (f instanceof IReversibleFlowFunction) {
|
||||
IReversibleFlowFunction rf = (IReversibleFlowFunction) f;
|
||||
IntSet sources = rf.getSources(node.getFact());
|
||||
if (sources != null) {
|
||||
for (IntIterator ii = sources.intIterator(); ii.hasNext();) {
|
||||
int t = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(src, t));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assertions.UNREACHABLE("need to implement for non-reversible flow function " + f.getClass());
|
||||
}
|
||||
} else {
|
||||
// special logic for a return edge. dest is a return site
|
||||
for (Iterator<? extends T> it2 = supergraph.getCallSites(dest); it2.hasNext(); ) {
|
||||
T callBlock = it2.next();
|
||||
IFlowFunction f = flowFunctions.getReturnFlowFunction(callBlock,src,dest);
|
||||
if (f instanceof IReversibleFlowFunction) {
|
||||
IReversibleFlowFunction rf = (IReversibleFlowFunction) f;
|
||||
IntSet sources = rf.getSources(node.getFact());
|
||||
if (sources != null) {
|
||||
for (IntIterator ii = sources.intIterator(); ii.hasNext();) {
|
||||
int t = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(src, t));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assertions.UNREACHABLE("need to implement for non-reversible flow function " + f.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
public int getPredNodeCount(ExplodedSupergraphNode<T> node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
T dest = node.getSupergraphNode();
|
||||
int count = 0;
|
||||
for (Iterator<? extends T> it = supergraph.getPredNodes(dest); it.hasNext();) {
|
||||
T src = it.next();
|
||||
if (supergraph.classifyEdge(src, dest) != ISupergraph.RETURN_EDGE) {
|
||||
IFlowFunction f = getFlowFunction(src, dest);
|
||||
if (f instanceof IReversibleFlowFunction) {
|
||||
IReversibleFlowFunction rf = (IReversibleFlowFunction) f;
|
||||
IntSet sources = rf.getSources(node.getFact());
|
||||
if (sources != null) {
|
||||
count += sources.size();
|
||||
}
|
||||
} else {
|
||||
Assertions.UNREACHABLE("need to implement for non-reversible flow function");
|
||||
}
|
||||
} else {
|
||||
// special logic for a return edge
|
||||
Assertions.UNREACHABLE("TODO: Implement me!");
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public Iterator<ExplodedSupergraphNode<T>> getSuccNodes(ExplodedSupergraphNode<T> node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
T src = node.getSupergraphNode();
|
||||
HashSet<ExplodedSupergraphNode<T>> result = HashSetFactory.make(supergraph.getSuccNodeCount(src));
|
||||
for (Iterator<? extends T> it = supergraph.getSuccNodes(src); it.hasNext();) {
|
||||
T dest = it.next();
|
||||
if (supergraph.classifyEdge(src, dest) != ISupergraph.RETURN_EDGE) {
|
||||
IUnaryFlowFunction f = (IUnaryFlowFunction) getFlowFunction(src, dest);
|
||||
IntSet targets = f.getTargets(node.getFact());
|
||||
if (targets != null) {
|
||||
for (IntIterator ii = targets.intIterator(); ii.hasNext();) {
|
||||
int t = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(dest, t));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// special logic for a return edge. dest is a return site
|
||||
for (Iterator<? extends T> it2 = supergraph.getCallSites(dest); it2.hasNext(); ) {
|
||||
T callBlock = it2.next();
|
||||
IUnaryFlowFunction f = (IUnaryFlowFunction) flowFunctions.getReturnFlowFunction(callBlock,src,dest);
|
||||
IntSet targets = f.getTargets(node.getFact());
|
||||
if (targets != null) {
|
||||
for (IntIterator ii = targets.intIterator(); ii.hasNext();) {
|
||||
int t = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(dest, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
private IFlowFunction getFlowFunction(T src, T dest) {
|
||||
switch (supergraph.classifyEdge(src, dest)) {
|
||||
case ISupergraph.CALL_EDGE:
|
||||
return flowFunctions.getCallFlowFunction(src, dest);
|
||||
case ISupergraph.CALL_TO_RETURN_EDGE:
|
||||
Iterator callees = supergraph.getCalledNodes(src);
|
||||
if (callees.hasNext()) {
|
||||
return flowFunctions.getCallToReturnFlowFunction(src, dest);
|
||||
} else {
|
||||
return flowFunctions.getCallNoneToReturnFlowFunction(src, dest);
|
||||
}
|
||||
case ISupergraph.RETURN_EDGE:
|
||||
Assertions.UNREACHABLE();
|
||||
return null;
|
||||
// return flowFunctions.getReturnFlowFunction(src, dest);
|
||||
case ISupergraph.OTHER:
|
||||
return flowFunctions.getNormalFlowFunction(src, dest);
|
||||
default:
|
||||
Assertions.UNREACHABLE();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getSuccNodeCount(ExplodedSupergraphNode<T> node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node is null");
|
||||
}
|
||||
T src = node.getSupergraphNode();
|
||||
int count = 0;
|
||||
for (Iterator<? extends T> it = supergraph.getSuccNodes(src); it.hasNext();) {
|
||||
T dest = it.next();
|
||||
IUnaryFlowFunction f = (IUnaryFlowFunction) getFlowFunction(src, dest);
|
||||
IntSet targets = f.getTargets(node.getFact());
|
||||
if (targets != null) {
|
||||
count += targets.size();
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void addEdge(ExplodedSupergraphNode src, ExplodedSupergraphNode dst) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void removeEdge(ExplodedSupergraphNode src, ExplodedSupergraphNode dst) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void removeAllIncidentEdges(ExplodedSupergraphNode node) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void removeIncomingEdges(ExplodedSupergraphNode node) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void removeOutgoingEdges(ExplodedSupergraphNode node) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean hasEdge(ExplodedSupergraphNode<T> src, ExplodedSupergraphNode<T> dst) {
|
||||
for (Iterator it = getSuccNodes(src); it.hasNext();) {
|
||||
if (it.next().equals(dst)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ISupergraph<T,?> getSupergraph() {
|
||||
return supergraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the flowFunctions.
|
||||
*/
|
||||
public IFlowFunctionMap getFlowFunctions() {
|
||||
return flowFunctions;
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2002 - 2006 IBM Corporation.
|
||||
* 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 com.ibm.wala.dataflow.IFDS;
|
||||
|
||||
import com.ibm.wala.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* A node in the exploded supergraph
|
||||
*
|
||||
* Note that this representation is inefficient and should be used with care.
|
||||
*
|
||||
* @author sfink
|
||||
*/
|
||||
public class ExplodedSupergraphNode<T> {
|
||||
|
||||
@NonNull
|
||||
private final T supergraphNode;
|
||||
|
||||
private final int fact;
|
||||
|
||||
/**
|
||||
* @param supergraphNode
|
||||
* @param fact
|
||||
*/
|
||||
public ExplodedSupergraphNode(T supergraphNode, int fact) {
|
||||
this.supergraphNode = supergraphNode;
|
||||
this.fact = fact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object arg0) {
|
||||
if (arg0 == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass().equals(arg0.getClass())) {
|
||||
ExplodedSupergraphNode other = (ExplodedSupergraphNode) arg0;
|
||||
return supergraphNode.equals(other.supergraphNode) && fact == other.fact;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 8353 * supergraphNode.hashCode() + fact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return supergraphNode.toString() + "," + fact;
|
||||
}
|
||||
|
||||
public int getFact() {
|
||||
return fact;
|
||||
}
|
||||
|
||||
public T getSupergraphNode() {
|
||||
return supergraphNode;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,472 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2002 - 2006 IBM Corporation.
|
||||
* 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 com.ibm.wala.dataflow.IFDS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.ibm.wala.ipa.callgraph.CGNode;
|
||||
import com.ibm.wala.util.collections.CollectionFilter;
|
||||
import com.ibm.wala.util.collections.Filter;
|
||||
import com.ibm.wala.util.collections.FilterIterator;
|
||||
import com.ibm.wala.util.collections.HashMapFactory;
|
||||
import com.ibm.wala.util.collections.HashSetFactory;
|
||||
import com.ibm.wala.util.collections.Pair;
|
||||
import com.ibm.wala.util.debug.Assertions;
|
||||
import com.ibm.wala.util.debug.Trace;
|
||||
import com.ibm.wala.util.graph.impl.GraphInverter;
|
||||
import com.ibm.wala.util.graph.traverse.BFSPathFinder;
|
||||
|
||||
/**
|
||||
*
|
||||
* A realizable path in the exploded supergraph
|
||||
*
|
||||
* @author sfink
|
||||
*/
|
||||
public class ExplodedSupergraphPath<T> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* List<ExplodedSupergraphNode>
|
||||
*/
|
||||
private final List<ExplodedSupergraphNode<T>> outermostList;
|
||||
|
||||
/**
|
||||
* Map: Pair<ExplodedSupegraphNode,ExplodedSupergraphNode> -> List<ExplodedSupergraphNode>
|
||||
* for any call-to-return edge, we memoize a List which holds a SLVP by which
|
||||
* the callee gets to the return site.
|
||||
*/
|
||||
private final Map<Pair, List<ExplodedSupergraphNode<T>>> edge2SLVP = HashMapFactory.make();
|
||||
|
||||
/**
|
||||
* Should we skip over boring calls when iterating?
|
||||
*/
|
||||
private boolean skipBoringCalls = false;
|
||||
|
||||
private final ExplodedSupergraphWithSummaryEdges<T> esg;
|
||||
|
||||
/**
|
||||
* @param nodeList
|
||||
*/
|
||||
private ExplodedSupergraphPath(List<ExplodedSupergraphNode<T>> nodeList, ExplodedSupergraphWithSummaryEdges<T> esg) {
|
||||
this.outermostList = nodeList;
|
||||
this.esg = esg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author sfink
|
||||
*
|
||||
* warning: these paths can be exponentially long
|
||||
*/
|
||||
private final class PathIterator implements Iterator {
|
||||
|
||||
final private Iterator it;
|
||||
|
||||
public void remove() {
|
||||
Assertions.UNREACHABLE();
|
||||
}
|
||||
|
||||
PathIterator() {
|
||||
// Trace.println(ExplodedSupergraphPath.this);
|
||||
// callStack holds the set of caller nodes <CGNode> currently on the call
|
||||
// stack
|
||||
Stack<CGNode> callStack = new Stack<CGNode>();
|
||||
List<ExplodedSupergraphNode<T>> L = new ArrayList<ExplodedSupergraphNode<T>>(outermostList);
|
||||
for (int i = 0; i < L.size() - 1; i++) {
|
||||
ExplodedSupergraphNode<T> src = L.get(i);
|
||||
ExplodedSupergraphNode<T> dest = L.get(i + 1);
|
||||
if (esg.getSupergraph().isExit(src.getSupergraphNode())) {
|
||||
if (!callStack.isEmpty()) {
|
||||
callStack.pop();
|
||||
}
|
||||
} else {
|
||||
if (skipBoringCalls && src.getFact() == dest.getFact()) {
|
||||
continue;
|
||||
} else if (esg.getSupergraph().isCall(src.getSupergraphNode())
|
||||
&& esg.getSupergraph().getProcOf(src.getSupergraphNode()).equals(
|
||||
esg.getSupergraph().getProcOf(dest.getSupergraphNode()))) {
|
||||
CGNode srcNode = (CGNode) esg.getSupergraph().getProcOf(src.getSupergraphNode());
|
||||
// avoid recursive path blowup
|
||||
if (!callStack.contains(srcNode)) {
|
||||
callStack.push(srcNode);
|
||||
// splice the sublist into the main list.
|
||||
List slvp = findOrCreateSLVP(src, dest, callStack);
|
||||
if (slvp != null) {
|
||||
L.addAll(i + 1, findOrCreateSLVP(src, dest, callStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it = L.iterator();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
return it.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callStack Stack<CGNode> which should not be traversed into
|
||||
* @return null if none found
|
||||
*/
|
||||
private List<ExplodedSupergraphNode<T>> findOrCreateSLVP(ExplodedSupergraphNode<T> src, ExplodedSupergraphNode<T> dest, Stack<CGNode> callStack) {
|
||||
Pair p = Pair.make(src, dest);
|
||||
List<ExplodedSupergraphNode<T>> l = edge2SLVP.get(p);
|
||||
// a bit of a hack ... give up on some memoization
|
||||
if (l != null && !validInCallStack(l, callStack)) {
|
||||
l = null;
|
||||
}
|
||||
if (l == null) {
|
||||
l = computeSummarySLVP(src, dest, callStack);
|
||||
if (l != null) {
|
||||
// we might fail to find a path due to limitations in
|
||||
// how we cope with reflection
|
||||
edge2SLVP.put(p, l);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* a List of nodes is "valid" for a particular callStack iff none of the call
|
||||
* nodes in the list originate from a node in the call Stack
|
||||
*
|
||||
* @param l
|
||||
* a list of ExplodedSupergraphNode
|
||||
* @param callStack
|
||||
* Collection<CGNode>
|
||||
*/
|
||||
private boolean validInCallStack(List<ExplodedSupergraphNode<T>> l, Stack<CGNode> callStack) {
|
||||
Iterator<ExplodedSupergraphNode<T>> it = l.iterator();
|
||||
while (it.hasNext()) {
|
||||
ExplodedSupergraphNode<T> src = it.next();
|
||||
if (callStack.contains(esg.getSupergraph().getProcOf(src.getSupergraphNode()))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<ExplodedSupergraphNode>
|
||||
*/
|
||||
public Iterator iterator() {
|
||||
return new PathIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter which accepts exploded supergraph nodes with the universal (0)
|
||||
* factoid.
|
||||
*/
|
||||
private final static Filter zeroFactFilter = new Filter() {
|
||||
public boolean accepts(Object o) {
|
||||
ExplodedSupergraphNode node = (ExplodedSupergraphNode) o;
|
||||
return node.getFact() == 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* This object traverses an exploded supergraph with summary edges from a set
|
||||
* of sources, until it finds a sink.
|
||||
*
|
||||
* During this traversal, it will <em>NOT</em> traverse interprocedural
|
||||
* edges
|
||||
*
|
||||
* @author sfink
|
||||
*
|
||||
*/
|
||||
static class SLVPFinder<T> extends BFSPathFinder<ExplodedSupergraphNode<T>> {
|
||||
final ExplodedSupergraphWithSummaryEdges esg;
|
||||
|
||||
final Collection<CGNode> exclusions;
|
||||
|
||||
SLVPFinder(ExplodedSupergraphWithSummaryEdges<T> esg, Collection<ExplodedSupergraphNode<T>> sources, Collection<ExplodedSupergraphNode<T>> sinks, Collection<CGNode> exclusions) {
|
||||
super(esg, sources.iterator(), new CollectionFilter<ExplodedSupergraphNode<T>>(sinks));
|
||||
this.esg = esg;
|
||||
this.exclusions = exclusions;
|
||||
// Trace.println("FINDER");
|
||||
// Trace.printCollection("sources", sources);
|
||||
// Trace.printCollection("sinks", sinks);
|
||||
// Trace.printCollection("exclusions", exclusions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<ExplodedSupergraphNode<T>> getConnected(final ExplodedSupergraphNode<T> n) {
|
||||
|
||||
return new FilterIterator<ExplodedSupergraphNode<T>>(super.getConnected(n), new Filter() {
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean accepts(Object o) {
|
||||
ExplodedSupergraphNode<T> dest = (ExplodedSupergraphNode<T>) o;
|
||||
// if (exclusions.contains(new Pair(src, dest))) {
|
||||
// Trace.println("exclude " + src + " , " + dest);
|
||||
// }
|
||||
return sameProc(n, dest) && !exclusions.contains(esg.getSupergraph().getProcOf(n.getSupergraphNode()));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private final boolean sameProc(ExplodedSupergraphNode<T> a, ExplodedSupergraphNode<T> b) {
|
||||
final PartiallyCollapsedSupergraph supergraph = (PartiallyCollapsedSupergraph) esg.getSupergraph();
|
||||
return supergraph.getProcOf(a.getSupergraphNode()).equals(supergraph.getProcOf(b.getSupergraphNode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExplodedSupergraphNode<T>> find() {
|
||||
List<ExplodedSupergraphNode<T>> L = super.find();
|
||||
if (L == null) {
|
||||
return L;
|
||||
} else {
|
||||
Collections.reverse(L);
|
||||
return L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This object traverses an exploded supergraph with summary edges
|
||||
* <em>backwards</em> from a sink, until it finds an node with the universal
|
||||
* (0) factoid.
|
||||
*
|
||||
* During this traversal, it will <em>NOT</em> traverse return edges from a
|
||||
* caller into the callee. Thus the returned path will only go UP the call
|
||||
* stack, and never down.
|
||||
*
|
||||
* Note that we're guaranteed to find some universal factoid since <main,0>
|
||||
* exists.
|
||||
*
|
||||
* @author sfink
|
||||
*
|
||||
*/
|
||||
static class NoReturnBackwardsPathFinder<T> extends BFSPathFinder<ExplodedSupergraphNode<T>> {
|
||||
final ExplodedSupergraphWithSummaryEdges<T> esg;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
NoReturnBackwardsPathFinder(ExplodedSupergraphWithSummaryEdges<T> esg, ExplodedSupergraphNode<T> sink) {
|
||||
super(GraphInverter.invert(esg), Collections.singleton(sink).iterator(), zeroFactFilter);
|
||||
this.esg = esg;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<ExplodedSupergraphNode<T>> getConnected(ExplodedSupergraphNode<T> n) {
|
||||
ExplodedSupergraphNode src = n;
|
||||
PartiallyCollapsedSupergraph supergraph = (PartiallyCollapsedSupergraph) esg.getSupergraph();
|
||||
HashSet<ExplodedSupergraphNode<T>> result = HashSetFactory.make(esg.getPredNodeCount(n));
|
||||
// add facts from non-call exploded supergraph edges
|
||||
for (Iterator<? extends ExplodedSupergraphNode<T>> it = super.getConnected(n); it.hasNext();) {
|
||||
ExplodedSupergraphNode<T> dest = it.next();
|
||||
// remember that we're traversing the graph backwards!
|
||||
switch (supergraph.classifyEdge(dest.getSupergraphNode(), src.getSupergraphNode())) {
|
||||
case ISupergraph.RETURN_EDGE:
|
||||
if (DEBUG) {
|
||||
Trace.println("Exclude edge " + src + " " + dest);
|
||||
}
|
||||
// do nothing
|
||||
break;
|
||||
default:
|
||||
result.add(dest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Trace.printCollection("getConnected " + n, result);
|
||||
if (result.size() == 0) {
|
||||
if (n.toString().indexOf("BB[SSA]0") > -1) {
|
||||
if (n.toString().indexOf("k.read()I") > -1) {
|
||||
Iterator x = super.getConnected(n);
|
||||
System.err.println(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a realizable path in the exploded supergraph to a sink node, from any
|
||||
* exploded supergraph node which represents the universal (0) factoid.
|
||||
*
|
||||
* @return an ExplodedSupergraphPath found, or null if not found.
|
||||
*/
|
||||
public static <T> ExplodedSupergraphPath<T> findRealizablePath(ExplodedSupergraphWithSummaryEdges<T> esg, ExplodedSupergraphNode<T> sink) {
|
||||
BFSPathFinder<ExplodedSupergraphNode<T>> backwardsFinder = new NoReturnBackwardsPathFinder<T>(esg, sink);
|
||||
if (DEBUG) {
|
||||
Trace.println("find path to sink " + sink);
|
||||
}
|
||||
|
||||
List<ExplodedSupergraphNode<T>> L = backwardsFinder.find();
|
||||
|
||||
if (DEBUG) {
|
||||
Trace.println("got backwards path " + new ExplodedSupergraphPath<T>(L, esg));
|
||||
}
|
||||
if (L == null) {
|
||||
return null;
|
||||
}
|
||||
ExplodedSupergraphPath<T> result = new ExplodedSupergraphPath<T>(L, esg);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* for a call-to-return edge <src,dest>, return a List which holds a SLVP by
|
||||
* which the callee gets to the return site.
|
||||
*
|
||||
* Note: During the traversal, exclude any edges which are already in the call
|
||||
* stack.
|
||||
*
|
||||
* @return null if search fails to find a path
|
||||
*
|
||||
*/
|
||||
private List<ExplodedSupergraphNode<T>> computeSummarySLVP(ExplodedSupergraphNode<T> src, ExplodedSupergraphNode<T> dest, Collection<CGNode> callStack) {
|
||||
|
||||
// calledTargets := exploded nodes that are reached from the src by a
|
||||
// call.
|
||||
HashSet<ExplodedSupergraphNode<T>> calledTargets = HashSetFactory.make(3);
|
||||
for (Iterator<ExplodedSupergraphNode<T>> it = esg.getSuccNodes(src); it.hasNext();) {
|
||||
ExplodedSupergraphNode<T> target = it.next();
|
||||
if (esg.getSupergraph().classifyEdge(src.getSupergraphNode(), target.getSupergraphNode()) == ISupergraph.CALL_EDGE) {
|
||||
calledTargets.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<ExplodedSupergraphNode<T>> calledExitNodes = HashSetFactory.make(3);
|
||||
for (Iterator<ExplodedSupergraphNode<T>> it = esg.getPredNodes(dest); it.hasNext();) {
|
||||
ExplodedSupergraphNode<T> target = it.next();
|
||||
if (esg.getSupergraph().classifyEdge(target.getSupergraphNode(), dest.getSupergraphNode()) == ISupergraph.RETURN_EDGE) {
|
||||
calledExitNodes.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (calledTargets.isEmpty() || calledExitNodes.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Trace.println("fill in summary: " + src + " -> " + dest);
|
||||
Trace.printCollection("calledTargets ", calledTargets);
|
||||
}
|
||||
// find a path from any calledTarget to the dest.
|
||||
SLVPFinder<T> innerFinder = new SLVPFinder<T>(esg, calledTargets, calledExitNodes, callStack);
|
||||
List<ExplodedSupergraphNode<T>> subList = innerFinder.find();
|
||||
// under current algorithm subList might be null due to
|
||||
// failures to handle reflection well.
|
||||
// if (Assertions.verifyAssertions) {
|
||||
// Assertions._assert(subList != null);
|
||||
// }
|
||||
if (DEBUG) {
|
||||
if (subList == null) {
|
||||
Trace.println("null sublist");
|
||||
} else {
|
||||
Trace.printCollection("subList ", subList);
|
||||
}
|
||||
}
|
||||
return subList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuffer result = new StringBuffer("Outermost List: \n");
|
||||
if (outermostList == null) {
|
||||
return "null outermost list";
|
||||
}
|
||||
appendNumberedList(result, outermostList.iterator());
|
||||
for (Iterator it = edge2SLVP.entrySet().iterator(); it.hasNext();) {
|
||||
Map.Entry e = (Map.Entry) it.next();
|
||||
Pair p = (Pair) e.getKey();
|
||||
result.append("SLVP for " + p + "\n");
|
||||
List l = (List) e.getValue();
|
||||
appendNumberedList(result, l.iterator());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void appendNumberedList(StringBuffer result, Iterator it) {
|
||||
int i = 0;
|
||||
while (it.hasNext()) {
|
||||
i++;
|
||||
ExplodedSupergraphNode n = (ExplodedSupergraphNode) it.next();
|
||||
result.append(i + " " + n + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a brief(er) summary of a path, which includes: 1) only call and
|
||||
* return nodes, and 2) excludes call/return pairs in which no state
|
||||
* transitions occur
|
||||
*/
|
||||
public static <T> ExplodedSupergraphPath<T> summarize(ISupergraph<T,?> supergraph, ExplodedSupergraphPath<T> path) {
|
||||
pruneForCallReturn(supergraph, path);
|
||||
pruneBoringCalls(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a briefer summary of a path, which excludes call/return pairs in
|
||||
* which no state transitions occur
|
||||
* @throws IllegalArgumentException if path is null
|
||||
*/
|
||||
public static <T> void pruneBoringCalls(ExplodedSupergraphPath<T> path) {
|
||||
if (path == null) {
|
||||
throw new IllegalArgumentException("path is null");
|
||||
}
|
||||
path.skipBoringCalls = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a brief(er) summary of a path, which includes only call and return
|
||||
* nodes
|
||||
* @throws IllegalArgumentException if path is null
|
||||
*/
|
||||
public static <T> void pruneForCallReturn(ISupergraph<T,?> supergraph, ExplodedSupergraphPath<T> path) {
|
||||
if (path == null) {
|
||||
throw new IllegalArgumentException("path is null");
|
||||
}
|
||||
pruneListForCallReturn(supergraph, path.outermostList);
|
||||
for (Iterator<List<ExplodedSupergraphNode<T>>> it = path.edge2SLVP.values().iterator(); it.hasNext();) {
|
||||
List<ExplodedSupergraphNode<T>> l = it.next();
|
||||
pruneListForCallReturn(supergraph, l);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a brief(er) summary of a path, which includes only call and return
|
||||
* nodes
|
||||
* @throws IllegalArgumentException if L == null
|
||||
*/
|
||||
public static <T> List<ExplodedSupergraphNode<T>> pruneListForCallReturn(ISupergraph<T,?> supergraph, List<ExplodedSupergraphNode<T>> L) throws IllegalArgumentException {
|
||||
if (L == null) {
|
||||
throw new IllegalArgumentException("L == null");
|
||||
}
|
||||
for (int i = 0; i < L.size(); i++) {
|
||||
ExplodedSupergraphNode<T> n = L.get(i);
|
||||
if (!supergraph.isEntry(n.getSupergraphNode()) && !supergraph.isExit(n.getSupergraphNode())
|
||||
&& !supergraph.isCall(n.getSupergraphNode()) && !supergraph.isReturn(n.getSupergraphNode())) {
|
||||
L.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return L;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2002 - 2006 IBM Corporation.
|
||||
* 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 com.ibm.wala.dataflow.IFDS;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.ibm.wala.util.collections.HashSetFactory;
|
||||
import com.ibm.wala.util.collections.Iterator2Collection;
|
||||
import com.ibm.wala.util.debug.Assertions;
|
||||
import com.ibm.wala.util.intset.IntIterator;
|
||||
import com.ibm.wala.util.intset.IntSet;
|
||||
|
||||
/**
|
||||
* This version of the exploded supergraph includes summary edges as deduced by
|
||||
* the tabulation solver
|
||||
*
|
||||
* @author sfink
|
||||
*
|
||||
*/
|
||||
public class ExplodedSupergraphWithSummaryEdges<T> extends ExplodedSupergraph<T> {
|
||||
|
||||
private final TabulationSolver<T, ?> solver;
|
||||
|
||||
/**
|
||||
* @param supergraph
|
||||
* @param flowFunctions
|
||||
* @param solver
|
||||
*/
|
||||
public ExplodedSupergraphWithSummaryEdges(ISupergraph<T, ?> supergraph, IFlowFunctionMap<T> flowFunctions,
|
||||
TabulationSolver<T, ?> solver) {
|
||||
super(supergraph, flowFunctions);
|
||||
this.solver = solver;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.dataflow.IFDS.ExplodedSupergraph#getSuccNodes(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Iterator<ExplodedSupergraphNode<T>> getSuccNodes(ExplodedSupergraphNode<T> src) {
|
||||
if (src == null) {
|
||||
throw new IllegalArgumentException("src is null");
|
||||
}
|
||||
HashSet<ExplodedSupergraphNode<T>> result = HashSetFactory.make(5);
|
||||
result.addAll(Iterator2Collection.toCollection(super.getSuccNodes(src)));
|
||||
|
||||
// add facts from summary edges
|
||||
if (getSupergraph().isCall(src.getSupergraphNode())) {
|
||||
for (Iterator<? extends T> it = getSupergraph().getReturnSites(src.getSupergraphNode()); it.hasNext();) {
|
||||
T dest = it.next();
|
||||
Assertions.UNREACHABLE();
|
||||
IntSet summary = null;
|
||||
// IntSet summary = solver.getSummaryTargets(src.getSupergraphNode(), src.getFact(), dest);
|
||||
if (summary != null) {
|
||||
for (IntIterator ii = summary.intIterator(); ii.hasNext();) {
|
||||
int d2 = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(dest, d2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.dataflow.IFDS.ExplodedSupergraph#getPredNodes(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Iterator<ExplodedSupergraphNode<T>> getPredNodes(ExplodedSupergraphNode<T> dest) {
|
||||
if (dest == null) {
|
||||
throw new IllegalArgumentException("dest is null");
|
||||
}
|
||||
HashSet<ExplodedSupergraphNode<T>> result = HashSetFactory.make(5);
|
||||
result.addAll(Iterator2Collection.toCollection(super.getPredNodes(dest)));
|
||||
|
||||
// add facts from summary edges
|
||||
if (getSupergraph().isReturn(dest.getSupergraphNode())) {
|
||||
for (Iterator<? extends T> it = getSupergraph().getCallSites(dest.getSupergraphNode()); it.hasNext();) {
|
||||
T src = it.next();
|
||||
IntSet summary = solver.getSummarySources(dest.getSupergraphNode(), dest.getFact(), src);
|
||||
if (summary != null) {
|
||||
for (IntIterator ii = summary.intIterator(); ii.hasNext();) {
|
||||
int d1 = ii.next();
|
||||
result.add(new ExplodedSupergraphNode<T>(src, d1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.dataflow.IFDS.ExplodedSupergraph#getPredNodeCount(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int getPredNodeCount(ExplodedSupergraphNode<T> N) {
|
||||
return Iterator2Collection.toCollection(getPredNodes(N)).size();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see com.ibm.wala.dataflow.IFDS.ExplodedSupergraph#getSuccNodeCount(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int getSuccNodeCount(ExplodedSupergraphNode<T> N) {
|
||||
return Iterator2Collection.toCollection(getSuccNodes(N)).size();
|
||||
}
|
||||
}
|
|
@ -23,44 +23,39 @@ public interface IFlowFunctionMap<T> {
|
|||
/**
|
||||
* @param src
|
||||
* @param dest
|
||||
* @return the flow function for a "normal" edge in the supergraph from
|
||||
* src->dest
|
||||
* @return the flow function for a "normal" edge in the supergraph from src->dest
|
||||
*/
|
||||
public IUnaryFlowFunction getNormalFlowFunction(T src, T dest);
|
||||
|
||||
/**
|
||||
* @param src
|
||||
* @param dest
|
||||
* @return the flow function for a "call" edge in the supergraph from
|
||||
* src->dest
|
||||
* @param src the call block
|
||||
* @param dest the entry of the callee
|
||||
* @param ret the block that will be returned to, in the caller. This can be null .. signifying that facts can flow
|
||||
* into the callee but not return
|
||||
* @return the flow function for a "call" edge in the supergraph from src->dest
|
||||
*/
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest);
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest, T ret);
|
||||
|
||||
/**
|
||||
* @param call
|
||||
* supergraph node of the call instruction for this return edge.
|
||||
* @param call supergraph node of the call instruction for this return edge.
|
||||
* @param src
|
||||
* @param dest
|
||||
* @return the flow function for a "return" edge in the supergraph from
|
||||
* src->dest
|
||||
* @return the flow function for a "return" edge in the supergraph from src->dest
|
||||
*/
|
||||
public IFlowFunction getReturnFlowFunction(T call, T src, T dest);
|
||||
|
||||
|
||||
/**
|
||||
* @param src
|
||||
* @param dest
|
||||
* @return the flow function for a "call-to-return" edge in the supergraph
|
||||
* from src->dest
|
||||
* @return the flow function for a "call-to-return" edge in the supergraph from src->dest
|
||||
*/
|
||||
public IUnaryFlowFunction getCallToReturnFlowFunction(T src, T dest);
|
||||
|
||||
/**
|
||||
* @param src
|
||||
* @param dest
|
||||
* @return the flow function for a "call-to-return" edge in the supergraph
|
||||
* from src->dest, when the supergraph does not contain any callees of
|
||||
* src. This happens via, e.g., slicing.
|
||||
* @return the flow function for a "call-to-return" edge in the supergraph from src->dest, when the supergraph does
|
||||
* not contain any callees of src. This happens via, e.g., slicing.
|
||||
*/
|
||||
public IUnaryFlowFunction getCallNoneToReturnFlowFunction(T src, T dest);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ public class IdentityFlowFunctions<T> implements IFlowFunctionMap<T> {
|
|||
/*
|
||||
* @see com.ibm.wala.dataflow.IFDS.IFlowFunctionMap#getCallFlowFunction(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Deprecated
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest) {
|
||||
return IdentityFlowFunction.identity();
|
||||
}
|
||||
|
@ -70,4 +71,8 @@ public class IdentityFlowFunctions<T> implements IFlowFunctionMap<T> {
|
|||
return IdentityFlowFunction.identity();
|
||||
}
|
||||
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest, T ret) {
|
||||
return IdentityFlowFunction.identity();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -548,9 +548,25 @@ public class TabulationSolver<T, P> {
|
|||
if (DEBUG_LEVEL > 0) {
|
||||
System.err.println(" process callee: " + callee);
|
||||
}
|
||||
IUnaryFlowFunction f = flowFunctionMap.getCallFlowFunction(edge.target, callee);
|
||||
MutableSparseIntSet reached = MutableSparseIntSet.makeEmpty();
|
||||
// we modify this to handle each return site individually. Some types of problems
|
||||
// compute different flow functions for each return site.
|
||||
for (final T returnSite : returnSites) {
|
||||
IUnaryFlowFunction f = flowFunctionMap.getCallFlowFunction(edge.target, callee, returnSite);
|
||||
// reached := {d1} that reach the callee
|
||||
IntSet reached = computeFlow(edge.d2, f);
|
||||
IntSet r = computeFlow(edge.d2, f);
|
||||
if (r != null) {
|
||||
reached.addAll(r);
|
||||
}
|
||||
}
|
||||
// in some problems, we also want to consider flow into a callee that can never flow out
|
||||
// via a return. in this case, the return site is null.
|
||||
IUnaryFlowFunction f = flowFunctionMap.getCallFlowFunction(edge.target, callee, null);
|
||||
// reached := {d1} that reach the callee
|
||||
IntSet r = computeFlow(edge.d2, f);
|
||||
if (r != null) {
|
||||
reached.addAll(r);
|
||||
}
|
||||
if (DEBUG_LEVEL > 0) {
|
||||
System.err.println(" reached: " + reached);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ import com.ibm.wala.util.intset.SparseIntSet;
|
|||
*
|
||||
* TODO: optimize by building this edge as implicit in every flow function.
|
||||
*
|
||||
* This is deprecated ... it's confusing and non-general.
|
||||
*
|
||||
* @author sfink
|
||||
*/
|
||||
@Deprecated
|
||||
public class UniversalKillFlowFunction implements IReversibleFlowFunction {
|
||||
|
||||
private final static UniversalKillFlowFunction singleton = new UniversalKillFlowFunction();
|
||||
|
|
|
@ -46,6 +46,7 @@ public class ReachabilityFunctions<T> implements IFlowFunctionMap<T> {
|
|||
private ReachabilityFunctions() {
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest) {
|
||||
return FLOW_REACHES;
|
||||
}
|
||||
|
@ -74,4 +75,8 @@ public class ReachabilityFunctions<T> implements IFlowFunctionMap<T> {
|
|||
return FLOW_REACHES;
|
||||
}
|
||||
|
||||
public IUnaryFlowFunction getCallFlowFunction(T src, T dest, T ret) {
|
||||
return FLOW_REACHES;
|
||||
}
|
||||
|
||||
}
|
|
@ -24,8 +24,8 @@ import com.ibm.wala.util.debug.Assertions;
|
|||
*/
|
||||
public class SliceFunctions implements IPartiallyBalancedFlowFunctions<Statement> {
|
||||
|
||||
public IUnaryFlowFunction getCallFlowFunction(Statement src, Statement dest) {
|
||||
return ReachabilityFunctions.createReachabilityFunctions().getCallFlowFunction(src, dest);
|
||||
public IUnaryFlowFunction getCallFlowFunction(Statement src, Statement dest, Statement ret) {
|
||||
return ReachabilityFunctions.createReachabilityFunctions().getCallFlowFunction(src, dest, ret);
|
||||
}
|
||||
|
||||
public IUnaryFlowFunction getCallNoneToReturnFlowFunction(Statement src, Statement dest) {
|
||||
|
|
Loading…
Reference in New Issue