/* * Copyright (c) 2010, 2016, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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.sun.btrace.runtime; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; /** * This class allows building an arbitrary graph caller-callee relationship * @author Jaroslav Bachorik */ final class CallGraph { private static final Pattern MID_SPLIT_PTN = Pattern.compile("\\:\\:"); public static class Node { private final String id; private final Set<Edge> incoming = new HashSet<>(); private final Set<Edge> outgoing = new HashSet<>(); public Node(String id) { this.id = id; } public void addIncoming(Edge e) { incoming.add(e); } public void addOutgoing(Edge e) { outgoing.add(e); } public void removeIncoming(Edge e) { incoming.remove(e); } public void removeOutgoing(Edge e) { outgoing.remove(e); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Node other = (Node) obj; return !((this.id == null) ? (other.id != null) : !this.id.equals(other.id)); } @Override public int hashCode() { int hash = 7; hash = 11 * hash + (this.id != null ? this.id.hashCode() : 0); return hash; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Node{id='").append(id).append("'}"); sb.append("\n"); sb.append("incomming:\n"); sb.append("=============================\n"); for(Edge e : incoming) { sb.append(e.from.id).append("\n"); } sb.append("=============================\n"); sb.append("outgoing:\n"); for(Edge e : outgoing) { sb.append(e.to.id).append("\n"); } sb.append("=============================\n"); return sb.toString(); } } public static class Edge { private Node from; private Node to; public Edge(Node from, Node to) { this.from = from; this.to = to; } public void delete() { this.from.removeOutgoing(this); this.to.removeIncoming(this); } @Override @SuppressWarnings("ReferenceEquality") public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Edge other = (Edge) obj; if (this.from != other.from && (this.from == null || !this.from.equals(other.from))) { return false; } return !(this.to != other.to && (this.to == null || !this.to.equals(other.to))); } @Override public int hashCode() { int hash = 5; hash = 37 * hash + (this.from != null ? this.from.hashCode() : 0); hash = 37 * hash + (this.to != null ? this.to.hashCode() : 0); return hash; } } final private Set<Node> nodes = new HashSet<>(); final private Set<Node> startingNodes = new HashSet<>(); public static String methodId(String name, String desc) { return name + "::" + desc; } public static String[] method(String methodId) { if (methodId.contains("::")) { return MID_SPLIT_PTN.split(methodId); } return new String[0]; } public void addEdge(String fromId, String toId) { Node fromNode = null; Node toNode = null; for(Node n : nodes) { if (n.id.equals(fromId)) { fromNode = n; } if (n.id.equals(toId)) { toNode = n; } if (fromNode != null && toNode != null) break; } if (fromNode == null) { fromNode = new Node(fromId); nodes.add(fromNode); } if (toNode == null) { toNode = new Node(toId); nodes.add(toNode); } Edge e = new Edge(fromNode, toNode); fromNode.addOutgoing(e); toNode.addIncoming(e); } public void addStarting(Node n) { for(Node orig : nodes) { if (orig.equals(n)) { startingNodes.add(orig); return; } } startingNodes.add(n); nodes.add(n); } public boolean hasCycle() { Set<Node> looped = findCycles(); Set<Node> checkingSet = new HashSet<>(looped); checkingSet.retainAll(startingNodes); if (!checkingSet.isEmpty()) { // a starting node is part of the loop return true; } Deque<Node> processingQueue = new ArrayDeque<>(); for(Node n : startingNodes) { processingQueue.push(n); do { Node current = processingQueue.pop(); if (looped.contains(current)) { // there is a path leading from a starting node to the detected loop return true; } for(Edge e : current.outgoing) { processingQueue.push(e.to); } } while (!processingQueue.isEmpty()); } return false; } void callees(String name, String desc, Set<String> closure) { collectOutgoings(methodId(name, desc), closure); } void callers(String name, String desc, Set<String> closure) { collectIncomings(methodId(name, desc), closure); } private void collectOutgoings(String methodId, Set<String> closure) { for(Node n : nodes) { if (n.id.equals(methodId)) { for(Edge e : n.outgoing) { String id = e.to.id; if (!closure.contains(id)) { closure.add(id); collectOutgoings(id, closure); } } } } } private void collectIncomings(String methodId, Set<String> closure) { for(Node n : nodes) { if (n.id.equals(methodId)) { for(Edge e : n.incoming) { String id = e.from.id; if (!closure.contains(id)) { closure.add(id); collectIncomings(id, closure); } } } } } private Set<Node> findCycles() { if (nodes.size() < 2) return Collections.EMPTY_SET; Map<String, Node> checkingNodes = new HashMap<>(); for(Node n : nodes) { Node newN = checkingNodes.get(n.id); if (newN == null) { newN = new Node(n.id); checkingNodes.put(n.id, newN); } for(Edge e : n.incoming) { Node fromN = checkingNodes.get(e.from.id); if (fromN == null) { fromN = new Node(e.from.id); checkingNodes.put(e.from.id, fromN); } Edge ee = new Edge(fromN, newN); newN.addIncoming(ee); fromN.addOutgoing(ee); } for(Edge e : n.outgoing) { Node toN = checkingNodes.get(e.to.id); if (toN == null) { toN = new Node(e.to.id); checkingNodes.put(e.to.id, toN); } Edge ee = new Edge(newN, toN); newN.addOutgoing(ee); toN.addIncoming(ee); } } boolean changesMade = false; Set<Node> sortedNodes = new HashSet<>(checkingNodes.values()); do { changesMade = false; Iterator<Node> iter = sortedNodes.iterator(); while (iter.hasNext()) { Node n = iter.next(); if ((n.incoming.isEmpty() && !startingNodes.contains(n)) || n.outgoing.isEmpty()) { changesMade = true; for(Edge e : new HashSet<>(n.incoming)) { e.delete(); } for (Edge e : new HashSet<>(n.outgoing)) { e.delete(); } iter.remove(); } } } while (changesMade); return sortedNodes; } }