/****************************************************************************** * 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> * { * "<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. * * @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(); } }