2012-07-31 18:31:34 +00:00
|
|
|
/******************************************************************************
|
|
|
|
* 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.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.
|
|
|
|
*
|
2012-08-28 20:12:01 +00:00
|
|
|
<<<<<<< HEAD
|
2012-07-31 18:31:34 +00:00
|
|
|
* The serialised objects have the form
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* {
|
|
|
|
* "<callsite1>": [ "<callee1>", "<callee2>", ... ],
|
|
|
|
* "<callsite2>": ...
|
|
|
|
* }
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* where both call sites and callees are encoded as strings
|
|
|
|
* of the form
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* "<filename>@<lineno>:<beginoff>-<endoff>"
|
|
|
|
* </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.
|
|
|
|
*
|
2012-08-28 20:12:01 +00:00
|
|
|
=======
|
|
|
|
>>>>>>> 4dc8bf18bc218aa65c36a5b36930bf0672561915
|
2012-07-31 18:31:34 +00:00
|
|
|
* @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 && methodName.startsWith("Lprologue.js/") || methodName.startsWith("Lpreamble.js/"))
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|