246 lines
8.1 KiB
Java
246 lines
8.1 KiB
Java
/******************************************************************************
|
|
* Copyright (c) 2011 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.cast.js.ipa.callgraph.correlations.extraction;
|
|
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.Stack;
|
|
|
|
import com.ibm.wala.cast.tree.CAst;
|
|
import com.ibm.wala.cast.tree.CAstControlFlowMap;
|
|
import com.ibm.wala.cast.tree.CAstEntity;
|
|
import com.ibm.wala.cast.tree.CAstNode;
|
|
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
|
|
import com.ibm.wala.cast.tree.impl.CAstControlFlowRecorder;
|
|
import com.ibm.wala.cast.tree.rewrite.CAstBasicRewriter.NoKey;
|
|
import com.ibm.wala.cast.tree.rewrite.CAstRewriter;
|
|
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;
|
|
|
|
/**
|
|
* Extension of {@link CAstRewriter} which allows adding or deleting control flow edges, and keeps track of
|
|
* the current entity.
|
|
*
|
|
* TODO: This class is an unholy mess. It should be restructured considerably.
|
|
*
|
|
* @author mschaefer
|
|
*
|
|
*/
|
|
public abstract class CAstRewriterExt extends CAstRewriter<NodePos, NoKey> {
|
|
|
|
/**
|
|
* A control flow edge to be added to the CFG.
|
|
*
|
|
* @author mschaefer
|
|
*/
|
|
protected static class Edge {
|
|
private CAstNode from;
|
|
private Object label;
|
|
private CAstNode to;
|
|
|
|
public Edge(CAstNode from, Object label, CAstNode to) {
|
|
assert from != null;
|
|
assert to != null;
|
|
this.from = from;
|
|
this.label = label;
|
|
this.to = to;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime + from.hashCode();
|
|
result = prime * result + ((label == null) ? 0 : label.hashCode());
|
|
result = prime * result + to.hashCode();
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if(!(obj instanceof Edge))
|
|
return false;
|
|
Edge that = (Edge)obj;
|
|
return this.from.equals(that.from) &&
|
|
(this.label == null ? that.label == null : this.label.equals(that.label)) &&
|
|
this.to.equals(that.to);
|
|
}
|
|
}
|
|
|
|
private final Map<CAstControlFlowMap, Set<CAstNode>> extra_nodes = HashMapFactory.make();
|
|
private final Map<CAstControlFlowMap, Set<Edge>> extra_flow = HashMapFactory.make();
|
|
private final Map<CAstControlFlowMap, Set<CAstNode>> flow_to_delete = HashMapFactory.make();
|
|
|
|
// information about an entity to add to the AST
|
|
private static class Entity {
|
|
private final CAstNode anchor;
|
|
private final CAstEntity me;
|
|
public Entity(CAstNode anchor, CAstEntity me) {
|
|
assert me != null;
|
|
this.anchor = anchor;
|
|
this.me = me;
|
|
}
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = prime + ((anchor == null) ? 0 : anchor.hashCode());
|
|
return prime * result + me.hashCode();
|
|
}
|
|
}
|
|
private final HashSet<Entity> entities_to_add = HashSetFactory.make();
|
|
private final Stack<CAstEntity> entities = new Stack<>();
|
|
|
|
public CAstNode addNode(CAstNode node, CAstControlFlowMap flow) {
|
|
Set<CAstNode> nodes = extra_nodes.get(flow);
|
|
if(nodes == null)
|
|
extra_nodes.put(flow, nodes = HashSetFactory.make());
|
|
nodes.add(node);
|
|
return node;
|
|
}
|
|
|
|
public CAstNode addFlow(CAstNode node, Object label, CAstNode target, CAstControlFlowMap flow) {
|
|
Set<Edge> edges = extra_flow.get(flow);
|
|
if(edges == null)
|
|
extra_flow.put(flow, edges = HashSetFactory.make());
|
|
edges.add(new Edge(node, label, target));
|
|
return node;
|
|
}
|
|
|
|
public void deleteFlow(CAstNode node, CAstEntity entity) {
|
|
CAstControlFlowMap flow = entity.getControlFlow();
|
|
Set<CAstNode> tmp = flow_to_delete.get(flow);
|
|
if(tmp == null)
|
|
flow_to_delete.put(flow, tmp = HashSetFactory.make());
|
|
tmp.add(node);
|
|
}
|
|
|
|
protected boolean isFlowDeleted(CAstNode node, CAstEntity entity) {
|
|
CAstControlFlowMap flow = entity.getControlFlow();
|
|
return flow_to_delete.containsKey(flow) && flow_to_delete.get(flow).contains(node);
|
|
}
|
|
|
|
public CAstEntity getCurrentEntity() {
|
|
return entities.peek();
|
|
}
|
|
|
|
public Iterable<CAstEntity> getEnclosingEntities() {
|
|
return entities;
|
|
}
|
|
|
|
public void addEntity(CAstNode anchor, CAstEntity entity) {
|
|
entities_to_add.add(new Entity(anchor, entity));
|
|
}
|
|
|
|
@Override
|
|
protected Map<CAstNode,Collection<CAstEntity>> copyChildren(CAstNode root, Map<Pair<CAstNode,NoKey>,CAstNode> nodeMap, Map<CAstNode,Collection<CAstEntity>> children) {
|
|
Map<CAstNode, Collection<CAstEntity>> map = super.copyChildren(root, nodeMap, children);
|
|
// extend with local mapping information
|
|
for(Iterator<Entity> es = entities_to_add.iterator(); es.hasNext(); ) {
|
|
Entity e = es.next();
|
|
boolean relevant = NodePos.inSubtree(e.anchor, nodeMap.get(Pair.make(root, null))) || NodePos.inSubtree(e.anchor, root);
|
|
if(relevant) {
|
|
Collection<CAstEntity> c = map.get(e.anchor);
|
|
if(c == null)
|
|
map.put(e.anchor, c = HashSetFactory.make());
|
|
c.add(e.me);
|
|
es.remove();
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
@Override
|
|
protected CAstNode flowOutTo(Map<Pair<CAstNode, NoKey>, CAstNode> nodeMap,
|
|
CAstNode oldSource, Object label, CAstNode oldTarget,
|
|
CAstControlFlowMap orig, CAstSourcePositionMap src) {
|
|
if(oldTarget == CAstControlFlowMap.EXCEPTION_TO_EXIT)
|
|
return oldTarget;
|
|
Assertions.UNREACHABLE();
|
|
return super.flowOutTo(nodeMap, oldSource, label, oldTarget, orig, src);
|
|
}
|
|
|
|
@Override
|
|
protected CAstControlFlowMap copyFlow(Map<Pair<CAstNode,NoKey>,CAstNode> nodeMap, CAstControlFlowMap orig, CAstSourcePositionMap newSrc) {
|
|
Map<Pair<CAstNode,NoKey>,CAstNode> nodeMapCopy = HashMapFactory.make(nodeMap);
|
|
// delete flow if necessary
|
|
// TODO: this is bad; what if one of the deleted nodes occurs as a cflow target?
|
|
if(flow_to_delete.containsKey(orig)) {
|
|
for(CAstNode node : flow_to_delete.get(orig)) {
|
|
nodeMapCopy.remove(Pair.make(node, null));
|
|
}
|
|
//flow_to_delete.remove(orig);
|
|
}
|
|
CAstControlFlowRecorder flow = (CAstControlFlowRecorder)super.copyFlow(nodeMapCopy, orig, newSrc);
|
|
// extend with local flow information
|
|
if(extra_nodes.containsKey(orig)) {
|
|
for(CAstNode nd : extra_nodes.get(orig))
|
|
flow.map(nd, nd);
|
|
}
|
|
if(extra_flow.containsKey(orig)) {
|
|
for(Edge e : extra_flow.get(orig)) {
|
|
CAstNode from = e.from;
|
|
Object label = e.label;
|
|
CAstNode to = e.to;
|
|
if(nodeMap.containsKey(Pair.make(from, null)))
|
|
from = nodeMap.get(Pair.make(from, null));
|
|
if(nodeMap.containsKey(Pair.make(to, null)))
|
|
to = nodeMap.get(Pair.make(to, null));
|
|
if(!flow.isMapped(from))
|
|
flow.map(from, from);
|
|
if(!flow.isMapped(to))
|
|
flow.map(to, to);
|
|
flow.add(from, to, label);
|
|
}
|
|
/*
|
|
* Here, we would like to say extra_flow.remove(orig) to get rid of the extra control flow
|
|
* information, but that would not be correct: a single old cfg may be carved up into several
|
|
* new ones, each of which needs to be extended with the appropriate extra flow from the old cfg.
|
|
*
|
|
* Unfortunately, we now end up extending _every_ new cfg with _all_ the extra flow from the old
|
|
* cfg, which doesn't sound right either.
|
|
*/
|
|
}
|
|
return flow;
|
|
}
|
|
|
|
Map<CAstEntity, CAstEntity> rewrite_cache = HashMapFactory.make();
|
|
@Override
|
|
public CAstEntity rewrite(CAstEntity root) {
|
|
// avoid rewriting the same entity more than once
|
|
// TODO: figure out why this happens in the first place
|
|
if(rewrite_cache.containsKey(root)) {
|
|
return rewrite_cache.get(root);
|
|
} else {
|
|
entities.push(root);
|
|
enterEntity(root);
|
|
CAstEntity entity = super.rewrite(root);
|
|
rewrite_cache.put(root, entity);
|
|
leaveEntity();
|
|
entities.pop();
|
|
return entity;
|
|
}
|
|
}
|
|
|
|
protected void enterEntity(@SuppressWarnings("unused") CAstEntity entity) {}
|
|
protected void leaveEntity() {}
|
|
|
|
public CAstRewriterExt(CAst Ast, boolean recursive, NodePos rootContext) {
|
|
super(Ast, recursive, rootContext);
|
|
}
|
|
|
|
}
|