/*
* Bytecode Analysis Framework
* Copyright (C) 2004 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.type;
import java.io.Serializable;
import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
/**
* Class for keeping track of exceptions that can be thrown by an instruction.
* We distinguish <em>explicit</em> and <em>implicit</em> exceptions. Explicit
* exceptions are explicitly declared, thrown, or caught. Implicit exceptions
* are runtime faults (NPE, array out of bounds) not explicitly handled by the
* user code.
*
* @author David Hovemeyer
* @see TypeAnalysis
*/
public class ExceptionSet implements Serializable {
private static final long serialVersionUID = 1;
private final ExceptionSetFactory factory;
private final BitSet exceptionSet;
private final BitSet explicitSet;
private int size;
private boolean universalHandler;
private Type commonSupertype;
/**
* Object to iterate over the exception types in the set.
*/
public class ThrownExceptionIterator implements Iterator<ObjectType> {
private int last = -1, next = -1;
ThrownExceptionIterator() {
findNext();
}
@Override
public boolean hasNext() {
if (last == next) {
findNext();
}
return next < factory.getNumTypes();
}
@Override
public ObjectType next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
ObjectType result = factory.getType(next);
last = next;
return result;
}
public boolean isExplicit() {
return explicitSet.get(last);
}
@Override
public void remove() {
exceptionSet.clear(last);
explicitSet.clear(last);
--size;
commonSupertype = null;
}
private void findNext() {
++next;
while (next < factory.getNumTypes()) {
if (exceptionSet.get(next)) {
break;
}
++next;
}
}
}
/**
* Constructor. Creates an empty set.
*/
ExceptionSet(ExceptionSetFactory factory) {
this.factory = factory;
this.exceptionSet = new BitSet();
this.explicitSet = new BitSet();
this.size = 0;
this.universalHandler = false;
}
/**
* Return an exact copy of this object.
*/
public ExceptionSet duplicate() {
ExceptionSet dup = factory.createExceptionSet();
dup.exceptionSet.clear();
dup.exceptionSet.or(this.exceptionSet);
dup.explicitSet.clear();
dup.explicitSet.or(this.explicitSet);
dup.size = this.size;
dup.universalHandler = this.universalHandler;
dup.commonSupertype = this.commonSupertype;
return dup;
}
@Override
public int hashCode() {
return exceptionSet.hashCode() + explicitSet.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o.getClass() != this.getClass()) {
return false;
}
ExceptionSet other = (ExceptionSet) o;
return exceptionSet.equals(other.exceptionSet) && explicitSet.equals(other.explicitSet)
&& universalHandler == other.universalHandler;
}
/**
* Get the least (lowest in the lattice) common supertype of the exceptions
* in the set. Returns the special TOP type if the set is empty.
*/
public Type getCommonSupertype() throws ClassNotFoundException {
if (commonSupertype != null) {
return commonSupertype;
}
if (isEmpty()) {
// This probably means that we're looking at an
// infeasible exception path.
return TypeFrame.getTopType();
}
// Compute first common superclass
ThrownExceptionIterator i = iterator();
ReferenceType result = i.next();
while (i.hasNext()) {
if (Subtypes2.ENABLE_SUBTYPES2_FOR_COMMON_SUPERCLASS_QUERIES) {
result = AnalysisContext.currentAnalysisContext().getSubtypes2().getFirstCommonSuperclass(result, i.next());
} else {
result = result.getFirstCommonSuperclass(i.next());
}
if (result == null) {
// This should only happen if the class hierarchy
// is incomplete. We'll just be conservative.
result = Type.THROWABLE;
break;
}
}
// Cache and return the result
commonSupertype = result;
return result;
}
/**
* Return an iterator over thrown exceptions.
*/
public ThrownExceptionIterator iterator() {
return new ThrownExceptionIterator();
}
/**
* Return whether or not the set is empty.
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Checks to see if the exception set is a singleton set containing just the
* named exception
*
* @param exceptionName
* (in dotted format)
* @return true if it is
*/
public boolean isSingleton(String exceptionName) {
if (size != 1) {
return false;
}
ObjectType e = iterator().next();
return e.toString().equals(exceptionName);
}
/**
* Add an explicit exception.
*
* @param type
* type of the exception
*/
public void addExplicit(ObjectType type) {
add(type, true);
}
/**
* Add an implicit exception.
*
* @param type
* type of the exception
*/
public void addImplicit(ObjectType type) {
add(type, false);
}
/**
* Add an exception.
*
* @param type
* the exception type
* @param explicit
* true if the exception is explicitly declared or thrown, false
* if implicit
*/
public void add(ObjectType type, boolean explicit) {
int index = factory.getIndexOfType(type);
if (!exceptionSet.get(index)) {
++size;
}
exceptionSet.set(index);
if (explicit) {
explicitSet.set(index);
}
commonSupertype = null;
}
/**
* Add all exceptions in the given set.
*
* @param other
* the set
*/
public void addAll(ExceptionSet other) {
exceptionSet.or(other.exceptionSet);
explicitSet.or(other.explicitSet);
size = countBits(exceptionSet);
commonSupertype = null;
}
private int countBits(BitSet bitSet) {
int count = 0;
for (int i = 0; i < factory.getNumTypes(); ++i) {
if (bitSet.get(i)) {
++count;
}
}
return count;
}
/**
* Remove all exceptions from the set.
*/
public void clear() {
exceptionSet.clear();
explicitSet.clear();
universalHandler = false;
commonSupertype = null;
size = 0;
}
/**
* Return whether or not a universal exception handler was reached by the
* set.
*/
public void sawUniversal() {
clear();
universalHandler = true;
}
/**
* Mark the set as having reached a universal exception handler.
*/
public boolean sawUniversalHandler() {
return universalHandler;
}
/**
* Return whether or not the set contains any checked exceptions.
*/
public boolean containsCheckedExceptions() throws ClassNotFoundException {
for (ThrownExceptionIterator i = iterator(); i.hasNext();) {
ObjectType type = i.next();
if (!Hierarchy.isUncheckedException(type)) {
return true;
}
}
return false;
}
/**
* Return whether or not the set contains any explicit exceptions.
*/
public boolean containsExplicitExceptions() {
for (ThrownExceptionIterator i = iterator(); i.hasNext();) {
i.next();
if (i.isExplicit()) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append('{');
boolean first = true;
for (ThrownExceptionIterator i = iterator(); i.hasNext();) {
ObjectType type = i.next();
if (first) {
first = false;
} else {
buf.append(',');
}
boolean implicit = !i.isExplicit();
if (implicit) {
buf.append('[');
}
buf.append(type.toString());
if (implicit) {
buf.append(']');
}
}
buf.append('}');
return buf.toString();
}
public int size() {
return size;
}
}