/**
* Copyright (C) 2013 Rohan Padhye
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package vasco;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* A record of transitions between contexts at call-sites.
*
* <p>The context transition table records a bidirectional one-to-many mapping
* of call-sites to called contexts parameterised by their methods.</p>
*
* <p>If a call-site transition is not traversed in an analysis (e.g. a call
* to a native method) then it is listed as a "default site" which this table
* also records.
*
*
* @author Rohan Padhye
*
* @param <M> the type of a method
* @param <N> the type of a node in the CFG
* @param <A> the type of a data flow value
*/
public class ContextTransitionTable<M,N,A> {
/** A map from contexts to a set of call-sites that transition to it. */
protected Map<Context<M,N,A>,Set<CallSite<M,N,A>>> callers;
/** A map from call-sites to contexts, parameterised by the called method. */
protected Map<CallSite<M,N,A>,Map<M,Context<M,N,A>>> transitions;
/** A map of contexts to call-sites present within their method bodies. */
protected Map<Context<M,N,A>,Set<CallSite<M,N,A>>> callSitesOfContexts;
/** A set of call-sites from which transitions are unknown. */
protected Set<CallSite<M,N,A>> defaultCallSites;
/** Constructs a new context transition table with no initial entries. */
public ContextTransitionTable() {
transitions = new HashMap<CallSite<M,N,A>,Map<M,Context<M,N,A>>>();
callers = new HashMap<Context<M,N,A>,Set<CallSite<M,N,A>>>();
callSitesOfContexts = new HashMap<Context<M,N,A>,Set<CallSite<M,N,A>>>();
defaultCallSites = new HashSet<CallSite<M,N,A>>();
}
/**
* Adds a transition to the table.
*
* <p>If the target context is specified as <tt>null</tt>, the source
* call-site is considered a "default site" from which transitions are
* unknown. This is used to model unpredictable targets, for example,
* when encountering calls to native methods.</p>
*
* <p>Any previous transitions from the source call site to other
* contexts of the same called method are deleted.</p>
*
* @param callSite the call-site which is the source of the transition
* @param targetContext the value context which is the target of the call-site
*/
public void addTransition(CallSite<M,N,A> callSite, Context<M,N,A> targetContext) {
if (targetContext != null) {
// Get the target method
M targetMethod = targetContext.getMethod();
// Ensure memory allocated in the reverse direction
if (!callers.containsKey(targetContext)) {
callers.put(targetContext, new HashSet<CallSite<M,N,A>>());
}
// Remove previous entry in the reverse direction
if (transitions.containsKey(callSite) && transitions.get(callSite).containsKey(targetMethod)) {
Context<M,N,A> oldTarget = transitions.get(callSite).get(targetMethod);
callers.get(oldTarget).remove(callSite);
}
// Ensure memory allocated in the forward direction
if (!transitions.containsKey(callSite)) {
transitions.put(callSite, new HashMap<M,Context<M,N,A>>());
}
// Make entry in the forward direction
transitions.get(callSite).put(targetMethod, targetContext);
// Make entry in the reverse direction
callers.get(targetContext).add(callSite);
} else {
// A null target means incomplete information (or "default")
// Remove previous entries in the reverse direction
if (transitions.containsKey(callSite) && transitions.get(callSite) != null) {
for (M method : transitions.get(callSite).keySet()) {
Context<M,N,A> oldTarget = transitions.get(callSite).get(method);
callers.get(oldTarget).remove(callSite);
}
}
// Add to default call sites
defaultCallSites.add(callSite);
}
// Ensure memory allocated for call-site index
Context<M,N,A> source = callSite.getCallingContext();
if (callSitesOfContexts.containsKey(source) == false) {
callSitesOfContexts.put(source, new HashSet<CallSite<M,N,A>>());
}
// Add call-site to source context
callSitesOfContexts.get(source).add(callSite);
}
/**
* Returns an unmodifiable view of the mapping from contexts to their callers.
* @return an unmodifiable view of the mapping from contexts to their callers
*/
public Map<Context<M,N,A>,Set<CallSite<M,N,A>>> getCallers() {
return Collections.unmodifiableMap(callers);
}
/**
* Returns the callers of a value context.
*
* @param target the target value context
* @return a set of call-sites which transition to the given target context
*/
public Set<CallSite<M,N,A>> getCallers(Context<M,N,A> target) {
return callers.get(target);
}
/**
* Returns an unmodifiable view of a mapping from calling contexts to all their call-sites.
* @return an unmodifiable view of a mapping from calling contexts to all their call-sites
*/
public Map<Context<M,N,A>,Set<CallSite<M,N,A>>> getCallSitesOfContexts() {
return Collections.unmodifiableMap(callSitesOfContexts);
}
/**
* Returns an unmodifiable view of the set of call-sites marked "default".
* @return an unmodifiable view of the set of call-sites marked "default"
*/
public Set<CallSite<M,N,A>> getDefaultCallSites() {
return defaultCallSites;
}
/**
* Returns the targets of a call-site.
*
* @param callSite the source of the transition
* @return a map of target methods to target contexts
*/
public Map<M,Context<M,N,A>> getTargets(CallSite<M,N,A> callSite) {
return transitions.get(callSite);
}
/**
* Returns an unmodifiable view of context transitions.
*
* @return an unmodifiable view of context transitions
*/
public Map<CallSite<M,N,A>,Map<M,Context<M,N,A>>> getTransitions() {
return Collections.unmodifiableMap(this.transitions);
}
/**
* Computes a reachable set of value contexts from a particular
* source by traversing the context transition table.
*
* Note that the source context itself is only reachable from
* itself if it there is a recursive call to it (i.e. a context
* is not reachable to itself by default).
*
* @param source the source context
* @return a set of contexts reachable from <tt>source</tt>
*/
public Set<Context<M,N,A>> reachableSet(Context<M,N,A> source, boolean ignoreFree) {
// The result set
Set<Context<M,N,A>> reachableContexts = new HashSet<Context<M,N,A>>();
// Maintain a stack of contexts to process
Stack<Context<M,N,A>> stack = new Stack<Context<M,N,A>>();
// Initialise it with the source
stack.push(source);
// Now recursively (using stacks) mark reachable contexts
while (stack.isEmpty() == false) {
// Get the next item to process
source = stack.pop();
// Add successors
if (callSitesOfContexts.containsKey(source)) {
// The above check is there because methods with no calls have no entry
for (CallSite<M,N,A> callSite : callSitesOfContexts.get(source)) {
// Don't worry about DEFAULT edges
if (defaultCallSites.contains(callSite)) {
continue;
}
for (M method : transitions.get(callSite).keySet()) {
Context<M,N,A> target = transitions.get(callSite).get(method);
// Don't process the same element twice
if (reachableContexts.contains(target) == false) {
// Are we ignoring free contexts?
if (ignoreFree && target.isFreed()) {
continue;
}
// Mark reachable
reachableContexts.add(target);
// Add it's successors also later
stack.push(target);
}
}
}
}
}
return reachableContexts;
}
}