edu.umd.cs.findbugs.plan.ExecutionPlan Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
The newest version!
/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2003-2008, University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.plan;
import java.util.*;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.DetectorFactory;
import edu.umd.cs.findbugs.DetectorFactoryChooser;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.FindBugs2;
import edu.umd.cs.findbugs.Plugin;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.graph.DepthFirstSearch;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
/**
* A plan for executing Detectors on an application. Automatically assigns
* Detectors to passes and orders Detectors within each pass based on ordering
* constraints specified in the plugin descriptor(s).
*
* @author David Hovemeyer
*/
public class ExecutionPlan {
public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.execplan.debug");
private List pluginList;
private DetectorFactoryChooser factoryChooser;
private LinkedList passList;
private Map factoryMap;
private List interPassConstraintList;
private List intraPassConstraintList;
private Set assignedToPassSet;
/**
* Constructor. Creates an empty plan.
*/
public ExecutionPlan() {
this.pluginList = new LinkedList<>();
this.factoryChooser = new DetectorFactoryChooser() {
@Override
public boolean choose(DetectorFactory factory) {
return true;
}
@Override
public void enable(DetectorFactory factory) {
// OK...
}
};
this.passList = new LinkedList<>();
this.factoryMap = new HashMap<>();
this.interPassConstraintList = new LinkedList<>();
this.intraPassConstraintList = new LinkedList<>();
this.assignedToPassSet = new HashSet<>();
}
public void dispose() {
pluginList.clear();
factoryChooser = null;
passList.clear();
factoryMap.clear();
interPassConstraintList.clear();
intraPassConstraintList.clear();
assignedToPassSet.clear();
}
/**
* Set the DetectorFactoryChooser to use to select which detectors to
* enable. This must be called before any Plugins are added to the execution
* plan.
*/
public void setDetectorFactoryChooser(DetectorFactoryChooser factoryChooser) {
this.factoryChooser = factoryChooser;
}
public boolean isActive(@DottedClassName String detectorClass) {
return factoryMap.containsKey(detectorClass);
}
public boolean isActive(Class extends Detector> detectorClass) {
return isActive(detectorClass.getName());
}
/**
* Add a Plugin whose Detectors should be added to the execution plan.
*/
public void addPlugin(Plugin plugin) throws OrderingConstraintException {
if (DEBUG) {
System.out.println("Adding plugin " + plugin.getPluginId() + " to execution plan");
}
pluginList.add(plugin);
// Add ordering constraints
copyTo(plugin.interPassConstraintIterator(), interPassConstraintList);
copyTo(plugin.intraPassConstraintIterator(), intraPassConstraintList);
// Add detector factories
for (DetectorFactory factory : plugin.getDetectorFactories()) {
if (DEBUG) {
System.out.println(" Detector factory " + factory.getShortName());
}
if (factoryMap.put(factory.getFullName(), factory) != null) {
throw new OrderingConstraintException("Detector " + factory.getFullName() + " is defined by more than one plugin");
}
}
}
/**
* Build the execution plan. Using the ordering constraints specified in the
* plugin descriptor(s), assigns Detectors to passes and orders the
* Detectors within those passes.
*/
public void build() throws OrderingConstraintException {
for (DetectorFactory detectorFactory : factoryMap.values()) {
detectorFactory.setEnabledButNonReporting(false);
}
ArrayList allConstraints = new ArrayList<>(
interPassConstraintList.size() + intraPassConstraintList.size());
allConstraints.addAll(interPassConstraintList);
allConstraints.addAll(intraPassConstraintList);
Map nodeMapAll = new HashMap<>();
ConstraintGraph allPassConstraintGraph = buildConstraintGraph(nodeMapAll,
new HashSet<>(factoryMap.values()), allConstraints);
boolean change;
do {
change = false;
for (Iterator i = allPassConstraintGraph.vertexIterator(); i.hasNext();) {
DetectorNode end = i.next();
if (factoryChooser.choose(end.getFactory())) {
for (Iterator j = allPassConstraintGraph.incomingEdgeIterator(end); j.hasNext();) {
ConstraintEdge edge = j.next();
DetectorNode start = edge.getSource();
DetectorFactory startFactory = start.getFactory();
//
// Note that we only enable an otherwise-disabled
// detector
// if it was the earlier detector in a single-source
// constraint.
//
if (!factoryChooser.choose(startFactory) && edge.isSingleSource()) {
factoryChooser.enable(startFactory);
change = true;
if (DEBUG || FindBugs2.DEBUG) {
System.out.println("Dependences force enabling of " + startFactory.getFullName());
}
}
}
}
}
} while (change);
factoryMap.entrySet().removeIf(e -> !factoryChooser.choose(e.getValue()));
// Build inter-pass constraint graph
Map nodeMap = new HashMap<>();
ConstraintGraph interPassConstraintGraph = buildConstraintGraph(nodeMap,
new HashSet<>(factoryMap.values()), interPassConstraintList);
if (DEBUG) {
System.out.println(interPassConstraintGraph.getNumVertices() + " nodes in inter-pass constraint graph");
}
// Build list of analysis passes.
// This will assign all detectors referenced in inter- or intra-pass
// ordering constraints to passes. Detectors with any ordering
// constraint will be left unassigned.
buildPassList(interPassConstraintGraph);
// Sort each pass by intra-pass ordering constraints.
// This may assign some previously unassigned detectors to passes.
for (AnalysisPass pass : passList) {
sortPass(intraPassConstraintList, factoryMap, pass);
}
// If there are any unassigned detectors remaining,
// add them to the final pass.
if (factoryMap.size() > assignedToPassSet.size()) {
AnalysisPass lastPass;
if (passList.isEmpty()) {
lastPass = new AnalysisPass();
addPass(lastPass);
} else {
lastPass = passList.getLast();
}
Set unassignedSet = getUnassignedSet();
for (DetectorFactory factory : unassignedSet) {
assignToPass(factory, lastPass);
}
appendDetectorsToPass(unassignedSet, lastPass);
}
if (DEBUG) {
print();
}
}
/**
* Get an Iterator over the AnalysisPasses.
*/
public Iterator passIterator() {
return passList.iterator();
}
/**
* Get the number of passes in the execution plan.
*
* @return the number of passes in the execution plan
*/
public int getNumPasses() {
return passList.size();
}
private static void copyTo(Iterator iter, Collection dest) {
while (iter.hasNext()) {
dest.add(iter.next());
}
}
/**
* Build a constraint graph. This represents ordering constraints between
* Detectors. A topological sort of the constraint graph will yield a
* correct ordering of the detectors (which may mean either passes or an
* ordering within a single pass, depending on whether the constraints are
* inter-pass or intra-pass).
*
* @param nodeMap
* map to be populated with detector class names to constraint
* graph nodes for those detectors
* @param factorySet
* build the graph using these DetectorFactories as nodes
* @param constraintList
* List of ordering constraints
* @return the ConstraintGraph
*/
private ConstraintGraph buildConstraintGraph(Map nodeMap, Set factorySet,
List constraintList) {
ConstraintGraph result = new ConstraintGraph();
for (DetectorOrderingConstraint constraint : constraintList) {
Set earlierSet = addOrCreateDetectorNodes(constraint.getEarlier(), nodeMap, factorySet, result);
Set laterSet = addOrCreateDetectorNodes(constraint.getLater(), nodeMap, factorySet, result);
createConstraintEdges(result, earlierSet, laterSet, constraint);
}
return result;
}
private Set selectDetectors(DetectorFactorySelector selector, Set candidateSet) {
Set result = new HashSet<>();
for (DetectorFactory factory : candidateSet) {
if (selector.selectFactory(factory)) {
result.add(factory);
}
}
return result;
}
private Set addOrCreateDetectorNodes(DetectorFactorySelector selector, Map nodeMap,
Set factorySet, ConstraintGraph constraintGraph) {
HashSet result = new HashSet<>();
Set chosenSet = selectDetectors(selector, factorySet);
for (DetectorFactory factory : chosenSet) {
DetectorNode node = addOrCreateDetectorNode(factory, nodeMap, constraintGraph);
result.add(node);
}
return result;
}
private DetectorNode addOrCreateDetectorNode(DetectorFactory factory, Map nodeMap,
ConstraintGraph constraintGraph) {
DetectorNode node = nodeMap.get(factory.getFullName());
if (node == null) {
node = new DetectorNode(factory);
nodeMap.put(factory.getFullName(), node);
constraintGraph.addVertex(node);
}
return node;
}
private void createConstraintEdges(ConstraintGraph result, Set earlierSet, Set laterSet,
DetectorOrderingConstraint constraint) {
// It is perfectly fine for a constraint to produce no edges
// if any detector it specifies is not enabled.
if (earlierSet.isEmpty() || laterSet.isEmpty()) {
return;
}
for (DetectorNode earlier : earlierSet) {
for (DetectorNode later : laterSet) {
ConstraintEdge edge = result.createEdge(earlier, later);
edge.setConstraint(constraint);
}
}
}
private void buildPassList(ConstraintGraph constraintGraph) throws OrderingConstraintException {
int passCount = 0;
while (constraintGraph.getNumVertices() > 0) {
List inDegreeZeroList = new LinkedList<>();
// Get all of the detectors nodes with in-degree 0.
// These have no unsatisfied prerequisites, and thus can
// be chosen for the current pass.
for (Iterator i = constraintGraph.vertexIterator(); i.hasNext();) {
DetectorNode node = i.next();
if (constraintGraph.getNumIncomingEdges(node) == 0) {
inDegreeZeroList.add(node);
} else if (DEBUG) {
System.out.println("Can't schedule " + node.getFactory().getShortName());
Iterator incomingEdgeIterator = constraintGraph.incomingEdgeIterator(node);
while (incomingEdgeIterator.hasNext()) {
ConstraintEdge edge = incomingEdgeIterator.next();
System.out.println(" requires " + edge.getSource().getFactory().getShortName());
}
}
}
if (inDegreeZeroList.isEmpty()) {
throw new OrderingConstraintException("Cycle in inter-pass ordering constraints");
}
// Remove all of the chosen detectors from the constraint graph.
for (DetectorNode node : inDegreeZeroList) {
constraintGraph.removeVertex(node);
}
// Create analysis pass and add detector factories.
// Note that this just makes the detectors members of the pass:
// it doesn't assign them a position in the pass.
AnalysisPass pass = new AnalysisPass();
addPass(pass);
passCount++;
for (DetectorNode node : inDegreeZeroList) {
assignToPass(node.getFactory(), pass);
}
}
}
private void addPass(AnalysisPass pass) {
if (DEBUG) {
System.out.println("Adding pass " + passList.size());
}
passList.add(pass);
}
private void sortPass(List constraintList, Map factoryMap,
AnalysisPass pass) throws OrderingConstraintException {
// Build set of all (initial) detectors in pass
Set detectorSet = new HashSet<>(pass.getMembers());
if (DEBUG) {
System.out.println(detectorSet.size() + " detectors currently in this pass");
}
// Build list of ordering constraints in this pass only
List passConstraintList = new LinkedList<>();
for (DetectorOrderingConstraint constraint : constraintList) {
// Does this constraint specify any detectors in this pass?
// If so, add it to the pass constraints
if (selectDetectors(constraint.getEarlier(), detectorSet).size() > 0
|| selectDetectors(constraint.getLater(), detectorSet).size() > 0) {
passConstraintList.add(constraint);
}
}
if (DEBUG) {
System.out.println(passConstraintList.size() + " constraints are applicable for this pass");
}
// Build set of all detectors available to be added to this pass
HashSet availableSet = new HashSet<>();
availableSet.addAll(detectorSet);
availableSet.addAll(getUnassignedSet());
// Build intra-pass constraint graph
Map nodeMap = new HashMap<>();
ConstraintGraph constraintGraph = buildConstraintGraph(nodeMap, availableSet, passConstraintList);
if (DEBUG) {
System.out.println("Pass constraint graph:");
dumpGraph(constraintGraph);
}
// See if any detectors were brought into the pass by an intrapass
// ordering constraint.
// Assign them to the pass officially.
for (DetectorNode node : nodeMap.values()) {
if (!pass.contains(node.getFactory())) {
assignToPass(node.getFactory(), pass);
}
}
// Perform DFS, check for cycles
DepthFirstSearch dfs = new DepthFirstSearch<>(
constraintGraph);
dfs.search();
if (dfs.containsCycle()) {
throw new OrderingConstraintException("Cycle in intra-pass ordering constraints!");
}
// Do a topological sort to put the detectors in the pass
// in the right order.
for (Iterator i = dfs.topologicalSortIterator(); i.hasNext();) {
DetectorNode node = i.next();
appendToPass(node.getFactory(), pass);
}
// Add any detectors not explicitly involved in intra-pass ordering
// constraints
// to the end of the pass.
appendDetectorsToPass(pass.getUnpositionedMembers(), pass);
}
private Set getUnassignedSet() {
Set unassignedSet = new HashSet<>(factoryMap.values());
unassignedSet.removeAll(assignedToPassSet);
return unassignedSet;
}
/**
* Make a DetectorFactory a member of an AnalysisPass.
*/
private void assignToPass(DetectorFactory factory, AnalysisPass pass) {
pass.addToPass(factory);
assignedToPassSet.add(factory);
}
/**
* Append a DetectorFactory to the end position in an AnalysisPass. The
* DetectorFactory must be a member of the pass.
*/
private void appendToPass(DetectorFactory factory, AnalysisPass pass) {
pass.append(factory);
}
private void appendDetectorsToPass(Collection detectorSet, AnalysisPass pass) {
DetectorFactory[] unassignedList = detectorSet.toArray(new DetectorFactory[0]);
Arrays.sort(unassignedList, (a, b) -> {
// Sort first by plugin id...
int cmp = a.getPlugin().getPluginId().compareTo(b.getPlugin().getPluginId());
if (cmp != 0) {
return cmp;
}
// Then by order specified in plugin descriptor
return a.getPositionSpecifiedInPluginDescriptor() - b.getPositionSpecifiedInPluginDescriptor();
});
for (DetectorFactory factory : unassignedList) {
appendToPass(factory, pass);
}
}
private void print() {
System.out.println("\nExecution plan:");
int passCount = 0;
for (Iterator i = passList.iterator(); i.hasNext(); ++passCount) {
System.out.println("Pass " + passCount);
AnalysisPass pass = i.next();
for (Iterator j = pass.iterator(); j.hasNext();) {
DetectorFactory factory = j.next();
System.out.println(" " + factory.getShortName());
}
}
System.out.println();
}
private void dumpGraph(ConstraintGraph graph) {
for (Iterator i = graph.edgeIterator(); i.hasNext();) {
ConstraintEdge edge = i.next();
System.out.println(edge.getSource().getFactory().getShortName() + " ==> "
+ edge.getTarget().getFactory().getShortName());
}
}
public static void main(String[] argv) throws Exception {
DetectorFactoryCollection detectorFactoryCollection = DetectorFactoryCollection.instance();
ExecutionPlan execPlan = new ExecutionPlan();
for (String pluginId : argv) {
Plugin plugin = detectorFactoryCollection.getPluginById(pluginId);
if (plugin != null) {
execPlan.addPlugin(plugin);
}
}
execPlan.build();
System.out.println(execPlan.getNumPasses() + " passes in plan");
execPlan.print();
}
}