com.ibm.wala.cast.js.util.CallGraph2JSON Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.ibm.wala.cast.js Show documentation
Show all versions of com.ibm.wala.cast.js Show documentation
T. J. Watson Libraries for Analysis
/*
* 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 com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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.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.ipa.callgraph.Context;
import com.ibm.wala.ipa.callgraph.impl.Everywhere;
import com.ibm.wala.ipa.callgraph.propagation.cfa.CallString;
import com.ibm.wala.ipa.callgraph.propagation.cfa.CallStringContext;
import com.ibm.wala.ipa.callgraph.propagation.cfa.CallStringContextSelector;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.Iterator2Iterable;
import com.ibm.wala.util.collections.MapUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Utility class to serialize call graphs as JSON objects.
*
* The serialised objects have the form
*
*
* {
* "<callsite1>": [ "<callee1>", "<callee2>", ... ],
* "<callsite2>": ...
* }
*
*
* where both call sites and callees are encoded as strings of the form
*
*
* "<filename>@<lineno>:<beginoff>-<endoff>"
*
*
* Here, {@code filename} is the name of the containing JavaScript file (not including its
* directory), and {@code lineno}, {@code beginoff} and {@code endoff} 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 {
/** ignore any calls to, from, or within WALA's harness containing models of natives methods */
private final boolean ignoreHarness;
/**
* if true, output JSON that keeps distinct method clones in the underlying call graph separate
*/
private final boolean exposeContexts;
public CallGraph2JSON() {
this(true);
}
public CallGraph2JSON(boolean ignoreHarness) {
this(ignoreHarness, false);
}
public CallGraph2JSON(boolean ignoreHarness, boolean exposeContexts) {
this.ignoreHarness = ignoreHarness;
this.exposeContexts = exposeContexts;
}
public String serialize(CallGraph cg) {
Map>> edges = extractEdges(cg);
return toJSON(edges);
}
/**
* Extract the edges of the given callgraph as a map over strings that is easy to serialize. The
* map keys are locations of methods. The map values are themselves maps, from call site locations
* within a method to the (locations of) potential target methods for the call sites.
*/
public Map>> extractEdges(CallGraph cg) {
Map>> edges = HashMapFactory.make();
for (CGNode nd : cg) {
if (!isValidFunctionFromSource(nd.getMethod())) {
continue;
}
IMethod method = nd.getMethod();
if (ignoreHarness && isHarnessMethod(method)) {
continue;
}
Map> edgesForMethod =
MapUtil.findOrCreateMap(edges, getJSONRepForNode(nd.getMethod(), nd.getContext()));
for (CallSiteReference callsite : Iterator2Iterable.make(nd.iterateCallSites())) {
serializeCallSite(nd, callsite, cg.getPossibleTargets(nd, callsite), edgesForMethod);
}
}
return edges;
}
public void serializeCallSite(
CGNode nd, CallSiteReference callsite, Set targets, Map> edges) {
Set targetNames =
MapUtil.findOrCreateSet(
edges, getJSONRepForCallSite(nd.getMethod(), nd.getContext(), callsite));
for (CGNode target : targets) {
IMethod trueTarget = getCallTargetMethod(target.getMethod());
if (trueTarget == null
|| !isValidFunctionFromSource(trueTarget)
|| (ignoreHarness && isHarnessMethod(trueTarget))) {
continue;
}
targetNames.add(getJSONRepForNode(trueTarget, target.getContext()));
}
}
private String getJSONRepForNode(IMethod method, Context context) {
String result;
if (isHarnessMethod(method) || isFunctionPrototypeCallOrApply(method)) {
// just use the method name; position is meaningless
result = getNativeMethodName(method);
} else {
AstMethod astMethod = (AstMethod) method;
result = astMethod.getSourcePosition().prettyPrint();
}
if (exposeContexts) {
result += getContextString(context);
}
return result;
}
private String getContextString(Context context) {
if (context.equals(Everywhere.EVERYWHERE)) {
return "";
} else if (context instanceof CallStringContext) {
CallStringContext cs = (CallStringContext) context;
CallString callString = (CallString) cs.get(CallStringContextSelector.CALL_STRING);
CallSiteReference csRef = callString.getCallSiteRefs()[0];
IMethod callerMethod = callString.getMethods()[0];
return " [" + getJSONRepForCallSite(callerMethod, Everywhere.EVERYWHERE, csRef) + "]";
} else {
throw new RuntimeException(context.toString());
}
}
private static String getNativeMethodName(IMethod method) {
String typeName = method.getDeclaringClass().getName().toString();
return typeName.substring(typeName.lastIndexOf('/') + 1) + " (Native)";
}
private String getJSONRepForCallSite(IMethod method, Context context, CallSiteReference site) {
String result;
if (isHarnessMethod(method) || isFunctionPrototypeCallOrApply(method)) {
result = getNativeMethodName(method);
} else {
AstMethod astMethod = (AstMethod) method;
result = astMethod.getSourcePosition(site.getProgramCounter()).prettyPrint();
}
if (exposeContexts) {
result += getContextString(context);
}
return result;
}
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;
}
private static boolean isValidFunctionFromSource(IMethod method) {
if (method instanceof AstMethod) {
String methodName = method.getDeclaringClass().getName().toString();
// exclude synthetic DOM modelling functions
if (methodName.contains("/make_node")) return false;
return method.getName().equals(AstMethodReference.fnAtom);
} else if (method.isWalaSynthetic()) {
if (isFunctionPrototypeCallOrApply(method)) {
return true;
}
}
return false;
}
private static boolean isFunctionPrototypeCallOrApply(IMethod method) {
String methodName = method.getDeclaringClass().getName().toString();
return methodName.equals("Lprologue.js/Function_prototype_call")
|| methodName.equals("Lprologue.js/Function_prototype_apply");
}
private static boolean isHarnessMethod(IMethod method) {
String methodName = method.getDeclaringClass().getName().toString();
for (String bootstrapFile : JavaScriptLoader.bootstrapFileNames) {
if (methodName.startsWith('L' + bootstrapFile + '/')) {
return true;
}
}
return false;
}
/**
* Converts a call graph map produced by {@link #extractEdges(CallGraph)} to JSON, eliding call
* sites with no targets.
*/
public static String toJSON(Map>> map) {
// strip out call sites with no targets
Map>> filtered = new HashMap<>();
for (Map.Entry>> entry : map.entrySet()) {
String methodLoc = entry.getKey();
Map> callSites = entry.getValue();
Map> filteredSites =
callSites.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!filteredSites.isEmpty()) {
filtered.put(methodLoc, filteredSites);
}
}
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(filtered);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy