/*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.max.graal.callanalysis;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import com.oracle.max.graal.compiler.debug.*;
import com.oracle.max.graal.compiler.debug.BasicIdealGraphPrinter.Edge;
import com.oracle.max.graal.compiler.graphbuilder.*;
import com.oracle.max.graal.cri.*;
import com.oracle.max.graal.nodes.*;
import com.sun.cri.ci.*;
import com.sun.cri.ri.*;
import com.sun.max.program.*;
/**
* Analyzes static calling relationships between methods, building a graph for the Ideal Graph Visualizer.
*/
public class StaticCallAnalyzer {
private final GraalRuntime runtime;
private final BasicIdealGraphPrinter printer;
private Set<RiResolvedType> knownTypes;
private Map<Class<?>, Set<Class<?>>> subtypeRelations;
private Map<RiResolvedMethod, Integer> knownMethods;
private Set<Edge> edges;
private int nextNodeId;
private int nextBlockId;
private final String[] includePrefixes;
private final ClassLoader loader;
/**
* Constructor for an analyzer that writes Ideal Graph Visualizer input to the specified stream, scans only classes
* with the given prefixes and uses the specified classloader to load them (if necessary).
*/
public StaticCallAnalyzer(OutputStream stream, String[] includePrefixes, ClassLoader loader) {
this.printer = new BasicIdealGraphPrinter(stream);
this.runtime = GraalRuntimeAccess.getGraalRuntime();
this.includePrefixes = Arrays.copyOf(includePrefixes, includePrefixes.length);
this.loader = loader;
}
/**
* Start a new graph document consisting of a single group with the specified title.
*/
public void start(String title) {
printer.begin();
printer.beginGroup();
printer.beginProperties();
printer.printProperty("name", title);
printer.printProperty("origin", "Graal Call Graph");
printer.endProperties();
printer.beginMethod(title, title, -1);
printer.endMethod();
}
/**
* Starts a new graph with the specified title.
*/
public void startGraph(String title) {
if (subtypeRelations == null) {
SubtypeDiscovery discovery = new SubtypeDiscovery(loader);
for (String prefix : includePrefixes) {
String path = prefix.replace('.', File.separatorChar);
discovery.run(Classpath.fromSystem(), path);
}
subtypeRelations = discovery.getSubtypeRelations();
}
printer.beginGraph(title);
printer.beginNodes();
knownTypes = new HashSet<RiResolvedType>();
knownMethods = new HashMap<RiResolvedMethod, Integer>();
edges = new HashSet<Edge>();
nextNodeId = 0;
nextBlockId = 0;
}
/**
* Analyzes the specified method of the specified class, and methods reachable from it, and adds corresponding
* nodes, edges and blocks to the graph.
*/
public void analyze(String className, String methodName) throws ClassNotFoundException {
analyze(findMethod(className, methodName));
}
/**
* Analyzes the specified method obtained by reflection, and methods reachable from it, and adds corresponding
* nodes, edges and blocks to the graph.
*/
public void analyze(Method method) {
analyze(getRiMethod(method));
}
/**
* Finishes the current graph started with {@link #startGraph(String)}.
*/
public void endGraph() {
printer.endNodes();
printer.beginEdges();
for (Edge edge : edges) {
printer.printEdge(edge);
}
printer.endEdges();
printer.beginControlFlow();
for (RiResolvedType type : knownTypes) {
printer.beginBlock(CiUtil.toJavaName(type, true));
printer.beginBlockNodes();
for (Entry<RiResolvedMethod, Integer> entry : knownMethods.entrySet()) {
RiResolvedMethod method = entry.getKey();
if (method.holder() == type) {
Integer nodeId = entry.getValue();
printer.printBlockNode(nodeId.toString());
}
}
printer.endBlockNodes();
printer.endBlock();
}
printer.endControlFlow();
printer.endGraph();
}
/**
* Finished the current graph document.
*/
public void end() {
printer.endGroup();
printer.end();
}
private Integer analyze(RiResolvedMethod method) {
Integer methodNodeId = knownMethods.get(method);
if (methodNodeId != null) {
// already analyzed earlier
return methodNodeId;
}
methodNodeId = createNodeForMethod(method);
final int flags = method.accessFlags();
if (Modifier.isNative(flags)) {
return methodNodeId;
}
if (includePrefixes != null) {
boolean included = false;
for (String pkg : includePrefixes) {
if (CiUtil.internalNameToJava(method.holder().name(), true).startsWith(pkg)) {
included = true;
break;
}
}
if (!included) {
return methodNodeId;
}
}
if (!Modifier.isPrivate(flags) && !Modifier.isStatic(flags) && !Modifier.isFinal(flags)) {
analyzeOverrides(method, methodNodeId);
}
if (Modifier.isAbstract(flags)) {
return methodNodeId;
}
StructuredGraph graph = buildGraph(method);
for (Invoke invoke : graph.getInvokes()) {
// TODO: Anonymous class instantiations
RiResolvedMethod target = invoke.callTarget().targetMethod();
Integer nodeId = analyze(target);
if (nodeId != null) {
edges.add(new Edge(methodNodeId.toString(), 0, nodeId.toString(), 0, "calls"));
}
}
return methodNodeId;
}
private int createNodeForMethod(RiResolvedMethod method) {
assert !knownMethods.containsKey(method);
knownTypes.add(method.holder());
int nodeId = nextNodeId++;
knownMethods.put(method, nodeId);
int flags = method.accessFlags();
boolean leaf = (Modifier.isFinal(flags) || Modifier.isPrivate(flags) || Modifier.isStatic(flags));
Map<String, String> properties = new HashMap<String, String>();
properties.put("idx", Integer.toString(nodeId));
properties.put("name", CiUtil.format("%n(%p): %r", method));
properties.put("qualifiedName", CiUtil.format("%n(%P): %R", method));
properties.put("class", CiUtil.format("%h", method));
properties.put("qualifiedClass", CiUtil.format("%H", method));
properties.put("native", Modifier.isNative(flags) ? "1" : "0");
properties.put("abstract", Modifier.isAbstract(flags) ? "1" : "0");
properties.put("leaf", leaf ? "1" : "0");
printer.printNode(Integer.toString(nodeId), properties);
return nodeId;
}
private void analyzeOverrides(RiResolvedMethod method, Integer methodNodeId) {
Class<?> clazz = method.holder().toJava();
if (!subtypeRelations.containsKey(clazz)) {
return;
}
// Build signature for reflection lookup
RiSignature signature = method.signature();
int nargs = signature.argumentCount(false);
Class<?>[] arguments = new Class<?>[nargs];
for (int i = 0; i < arguments.length; i++) {
Class< ? > type;
RiType ritype = signature.argumentTypeAt(i, null);
type = getClassForType(ritype);
arguments[i] = type;
}
analyzeOverrides(method, methodNodeId, arguments);
}
private void analyzeOverrides(RiResolvedMethod method, Integer methodNodeId, Class<?>[] arguments) {
Class<?> clazz = method.holder().toJava();
Collection<Class<?>> subtypes = subtypeRelations.get(clazz);
if (subtypes == null) {
return;
}
for (Class<?> subtype : subtypes) {
try {
RiResolvedMethod overridden = getRiMethod(subtype.getDeclaredMethod(method.name(), arguments));
Integer nodeId = analyze(overridden);
if (nodeId != null) {
String label = "overrides";
if (method.holder().isInterface()) {
label = "implements";
}
edges.add(new Edge(methodNodeId.toString(), 1, nodeId.toString(), 1, label));
analyzeOverrides(overridden, nodeId, arguments);
}
} catch (NoSuchMethodException e) {
// ignore
}
}
}
private Class<?> getClassForType(RiType riType) {
Class<?> clazz;
if (riType instanceof RiResolvedType) {
clazz = ((RiResolvedType) riType).toJava();
} else {
CiKind kind = riType.kind(false);
if (kind != CiKind.Object) {
clazz = kind.toJavaClass();
} else {
String name;
if (riType.name().charAt(0) == '[') {
// Array: Class.forName() expects names such as: [Ljava.lang.String;
name = riType.name().replace('/', '.');
} else {
name = CiUtil.toJavaName(riType);
}
try {
clazz = Class.forName(name, false, getClass().getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
return clazz;
}
private Method findMethod(String className, String methodName) throws ClassNotFoundException {
Class<?> clazz = Class.forName(className);
Method found = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
assert (found == null);
found = method;
}
}
return found;
}
private RiResolvedMethod getRiMethod(Method method) {
if (method == null) {
return null;
}
return runtime.getRiMethod(method);
}
private StructuredGraph buildGraph(RiResolvedMethod riMethod) {
StructuredGraph graph = new StructuredGraph();
new GraphBuilderPhase(runtime, riMethod, null, new GraphBuilderConfiguration(false, true, null)).apply(graph);
return graph;
}
}