WALA/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/util/CallGraph2JSON.java

179 lines
5.9 KiB
Java

/******************************************************************************
* Copyright (c) 2002 - 2012 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.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.cast.js.loader.JavaScriptLoader;
import com.ibm.wala.cast.js.types.JavaScriptMethods;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.types.AstMethodReference;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.MapUtil;
import com.ibm.wala.util.collections.Util;
import com.ibm.wala.util.functions.Function;
/**
* Utility class to serialize call graphs as JSON objects.
*
* The serialised objects have the form
*
* <pre>
* {
* "&lt;callsite1&gt;": [ "&lt;callee1&gt;", "&lt;callee2&gt;", ... ],
* "&lt;callsite2&gt;": ...
* }
* </pre>
*
* where both call sites and callees are encoded as strings
* of the form
*
* <pre>
* "&lt;filename&gt;@&lt;lineno&gt;:&lt;beginoff&gt;-&lt;endoff&gt;"
* </pre>
*
* Here, <code>filename</code> is the name of the containing
* JavaScript file (not including its directory), and
* <code>lineno</code>, <code>beginoff</code> and <code>endoff</code>
* encode the source position of the call expression (for
* call sites) or the function declaration/expression (for
* callees) inside the file in terms of its starting line,
* its starting offset (in characters from the beginning of the
* file), and its end offset.
*
* @author mschaefer
*/
public class CallGraph2JSON {
public static boolean IGNORE_HARNESS = true;
public static String serialize(CallGraph cg) {
Map<String, Set<String>> edges = extractEdges(cg);
return toJSON(edges);
}
public static Map<String, Set<String>> extractEdges(CallGraph cg) {
Map<String, Set<String>> edges = HashMapFactory.make();
for(CGNode nd : cg) {
if(!isRealFunction(nd.getMethod()))
continue;
AstMethod method = (AstMethod)nd.getMethod();
for(Iterator<CallSiteReference> iter = nd.iterateCallSites(); iter.hasNext();) {
CallSiteReference callsite = iter.next();
Set<IMethod> targets = Util.mapToSet(cg.getPossibleTargets(nd, callsite), new Function<CGNode, IMethod>() {
@Override
public IMethod apply(CGNode nd) {
return nd.getMethod();
}
});
serializeCallSite(method, callsite, targets, edges);
}
}
return edges;
}
public static void serializeCallSite(AstMethod method, CallSiteReference callsite, Set<IMethod> targets,
Map<String, Set<String>> edges) {
Set<String> targetNames = MapUtil.findOrCreateSet(edges, ppPos(method, method.getSourcePosition(callsite.getProgramCounter())));
for(IMethod target : targets) {
target = getCallTargetMethod(target);
if(!isRealFunction(target))
continue;
targetNames.add(ppPos((AstMethod)target, ((AstMethod)target).getSourcePosition()));
}
}
private static IMethod getCallTargetMethod(IMethod method) {
if(method.getName().equals(JavaScriptMethods.ctorAtom)) {
method = method.getDeclaringClass().getMethod(AstMethodReference.fnSelector);
if(method != null)
return method;
}
return method;
}
public static boolean isRealFunction(IMethod method) {
if(method instanceof AstMethod) {
String methodName = method.getDeclaringClass().getName().toString();
// exclude synthetic DOM modelling functions
if(methodName.contains("/make_node"))
return false;
if(IGNORE_HARNESS) {
for(String bootstrapFile : JavaScriptLoader.bootstrapFileNames)
if(methodName.startsWith("L" + bootstrapFile + "/"))
return false;
}
return method.getName().equals(AstMethodReference.fnAtom);
}
return false;
}
private static String ppPos(AstMethod method, Position pos) {
String file = pos.getURL().getFile();
file = file.substring(file.lastIndexOf('/')+1);
int line = pos.getFirstLine(), start_offset = pos.getFirstOffset(), end_offset = pos.getLastOffset();
return file + "@" + line + ":" + start_offset + "-" + end_offset;
}
public static String toJSON(Map<String, Set<String>> map) {
StringBuffer res = new StringBuffer();
res.append("{\n");
res.append(joinWith(Util.mapToSet(map.entrySet(), new Function<Map.Entry<String, Set<String>>, String>() {
@Override
public String apply(Map.Entry<String, Set<String>> e) {
StringBuffer res = new StringBuffer();
if(e.getValue().size() > 0) {
res.append(" \"" + e.getKey() + "\": [\n");
res.append(joinWith(Util.mapToSet(e.getValue(), new Function<String, String>() {
@Override
public String apply(String str) {
return " \"" + str + "\"";
}
}), ",\n"));
res.append("\n ]");
}
return res.length() == 0 ? null : res.toString();
}
}), ",\n"));
res.append("\n}");
return res.toString();
}
private static String joinWith(Iterable<String> lst, String sep) {
StringBuffer res = new StringBuffer();
ArrayList<String> strings = new ArrayList<String>();
for(String s : lst)
if(s != null)
strings.add(s);
boolean fst = true;
for(String s : strings) {
if(fst)
fst = false;
else
res.append(sep);
res.append(s);
}
return res.toString();
}
}