/* * 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. * * <p> * Invariant: no StateSet may contain more than one State with the same * ObligationSet. * </p> * * <p> * See Weimer and Necula, <a href="http://doi.acm.org/10.1145/1028976.1029011" * >Finding and preventing run-time error handling mistakes</a>, OOPSLA 2004. * </p> * * @author David Hovemeyer */ public class StateSet { private boolean isTop; private boolean isBottom; private boolean onExceptionPath; private Map<ObligationSet, State> stateMap; public boolean isEmpty() { return stateMap.isEmpty(); } private final ObligationFactory factory; public StateSet(ObligationFactory factory) { this.isTop = this.isBottom = false; this.stateMap = new HashMap<ObligationSet, State>(); 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<State> stateIterator() { return stateMap.values().iterator(); } /** * Get Set of all ObligationsSets in this StateSet. * * @return Set of all ObligationsSets in this StateSet */ public Set<ObligationSet> 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<ObligationSet, State> updatedStateMap = new HashMap<ObligationSet, State>(); 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<ObligationSet, State> updatedStateMap = new HashMap<ObligationSet, State>(); for (Iterator<State> 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<ObligationSet, State> 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<State> getPrefixStates(Path path) { List<State> result = new LinkedList<State>(); 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<ObligationSet, State> createEmptyMap() { return new HashMap<ObligationSet, State>(); } } // vim:ts=4