edu.umd.cs.findbugs.ba.obl.StateSet 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!
/*
* Bytecode Analysis Framework
* Copyright (C) 2005,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.ba.obl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.ba.Path;
/**
* A dataflow fact used in ObligationAnalysis. It is a set of State objects,
* plus the additional capability to represent top and bottom elements.
*
*
* Invariant: no StateSet may contain more than one State with the same
* ObligationSet.
*
*
*
* See Weimer and Necula, Finding and preventing run-time error handling mistakes, OOPSLA 2004.
*
*
* @author David Hovemeyer
*/
public class StateSet {
private boolean isTop;
private boolean isBottom;
private boolean onExceptionPath;
private Map stateMap;
public boolean isEmpty() {
return stateMap.isEmpty();
}
private final ObligationFactory factory;
public StateSet(ObligationFactory factory) {
this.isTop = this.isBottom = false;
this.stateMap = new HashMap<>();
this.factory = factory;
}
public void setTop() {
this.isTop = true;
this.isBottom = false;
this.onExceptionPath = false;
this.stateMap.clear();
}
public boolean isTop() {
return isTop;
}
public void setBottom() {
this.isBottom = true;
this.isTop = false;
}
public boolean isBottom() {
return this.isBottom;
}
public boolean isValid() {
return !this.isTop && !this.isBottom;
}
public boolean isOnExceptionPath() {
return onExceptionPath;
}
public void setOnExceptionPath(boolean onExceptionPath) {
this.onExceptionPath = onExceptionPath;
}
public void clear() {
this.isTop = this.isBottom = this.onExceptionPath = false;
stateMap.clear();
}
/**
* Return an Iterator over the States in the StateSet.
*
* @return an Iterator over the States in the StateSet
*/
public Iterator stateIterator() {
return stateMap.values().iterator();
}
/**
* Get Set of all ObligationsSets in this StateSet.
*
* @return Set of all ObligationsSets in this StateSet
*/
public Set getAllObligationSets() {
return Collections.unmodifiableSet(stateMap.keySet());
}
/**
* Get the State which has the given ObligationSet. Returns null if there is
* no such state.
*
* @param obligationSet
* we want to get the State with this ObligationSet
* @return the State with the given ObligationSet, or null if there is no
* such State
*/
public State getStateWithObligationSet(ObligationSet obligationSet) {
return stateMap.get(obligationSet);
}
// /**
// * Initialize this object as the entry fact for a method:
// * a single state with empty obligation set and path.
// *
// * @param factory the ObligationFactory used for the analysis
// */
// public void initEntryFact(ObligationFactory factory) {
// this.isTop = this.isBottom = false;
// this.stateMap.clear();
//
// // Add initial fact: empty obligations, empty path
// State initState = new State(factory);
// this.stateMap.put(initState.getObligationSet(), initState);
// }
/**
* Make this StateSet an exact copy of the given StateSet.
*
* @param other
* a StateSet; this StateSet will be made identical to it
*/
public void copyFrom(StateSet other) {
this.isTop = other.isTop;
this.isBottom = other.isBottom;
this.onExceptionPath = other.onExceptionPath;
this.stateMap.clear();
for (State state : other.stateMap.values()) {
State dup = state.duplicate();
this.stateMap.put(dup.getObligationSet(), dup);
}
}
/**
* Return an exact deep copy of this StateSet.
*
* @return an exact deep copy of this StateSet
*/
public StateSet duplicate() {
StateSet dup = new StateSet(factory);
dup.copyFrom(this);
return dup;
}
/**
* Add an obligation to every State in the StateSet.
*
* @param obligation
* the obligation to add
* @param basicBlockId
* the id of the basic block (path component) adding the
* obligation
*/
public void addObligation(final Obligation obligation, int basicBlockId) throws ObligationAcquiredOrReleasedInLoopException {
Map updatedStateMap = new HashMap<>();
if (stateMap.isEmpty()) {
State s = new State(factory);
s.getObligationSet().add(obligation);
updatedStateMap.put(s.getObligationSet(), s);
} else {
for (State state : stateMap.values()) {
checkCircularity(state, obligation, basicBlockId);
state.getObligationSet().add(obligation);
updatedStateMap.put(state.getObligationSet(), state);
}
}
replaceMap(updatedStateMap);
}
/**
* Remove an Obligation from every State in the StateSet.
*
* @param obligation
* the obligation to remove
* @param basicBlockId
* the id of the basic block (path component) removing the
* obligation
* @throws ObligationAcquiredOrReleasedInLoopException
*/
public void deleteObligation(final Obligation obligation, int basicBlockId)
throws ObligationAcquiredOrReleasedInLoopException {
Map updatedStateMap = new HashMap<>();
for (Iterator i = stateIterator(); i.hasNext();) {
State state = i.next();
checkCircularity(state, obligation, basicBlockId);
ObligationSet obligationSet = state.getObligationSet();
obligationSet.remove(obligation);
if (!obligationSet.isEmpty()) {
updatedStateMap.put(obligationSet, state);
}
}
replaceMap(updatedStateMap);
}
/**
* Bail out of the analysis is an obligation is acquired or released in a
* loop.
*
* @param state
* a State to which an obligation is being added or removed
* @param obligation
* the Obligation being added or removed
* @param basicBlockId
* the id of the BasicBlock adding or removing the obligation
*/
private void checkCircularity(State state, Obligation obligation, int basicBlockId)
throws ObligationAcquiredOrReleasedInLoopException {
if (state.getPath().hasComponent(basicBlockId)) {
throw new ObligationAcquiredOrReleasedInLoopException(obligation);
}
}
/**
* Replace the map of ObligationSets to States with the given one.
*
* @param stateMap
* enw map of ObligationSets to States
*/
public void replaceMap(Map stateMap) {
this.stateMap = stateMap;
}
/**
* Get all States that have Paths which are prefixes of the given Path.
*
* @param path
* a Path
* @return Collection of States that have Paths which are prefixes of the
* given Path
*/
public List getPrefixStates(Path path) {
List result = new LinkedList<>();
for (State state : stateMap.values()) {
if (state.getPath().isPrefixOf(path)) {
result.add(state);
}
}
return result;
}
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != this.getClass()) {
return false;
}
StateSet other = (StateSet) o;
return this.isTop == other.isTop && this.isBottom == other.isBottom
&& this.onExceptionPath == other.onExceptionPath && this.stateMap.equals(other.stateMap);
}
@Override
public int hashCode() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
if (isTop) {
return "TOP";
} else if (isBottom) {
return "BOTTOM";
} else {
StringBuilder buf = new StringBuilder();
buf.append(stateMap);
if (onExceptionPath) {
buf.append(" On exception path");
}
return buf.toString();
}
}
/**
* Return a newly allocated Map of ObligationSet to State that may be passed
* to applyToAllStatesAndUpdateMap().
*/
public Map createEmptyMap() {
return new HashMap<>();
}
}