/* Soot - a J*va Optimization Framework
* Copyright (C) 2003 John Jorgensen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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 soot.toolkits.exceptions;
import soot.*;
import soot.options.Options;
import java.util.*;
/**
* <p>A class for representing the set of exceptions that an
* instruction may throw.</p>
*
* <p> <code>ThrowableSet</code> does not implement the
* {@link java.util.Set} interface, so perhaps it is misnamed.
* Instead, it provides only the operations that we require for
* determining whether a given statement might throw an exception that
* would be caught by a given handler.</p>
*
* <p>There is a limitation on the combinations of operations
* permitted on a <code>ThrowableSet</code>. The
* <code>ThrowableSet</code>s returned by {@link
* #whichCatchableAs(RefType)} cannot be involved in subsequent
* <code>add()</code> or <code>whichCatchableAs()</code> operations.
* That is, given
*
* <blockquote>
* <code>p = s.whichCatchableAs(r)</code>
* </blockquote>
*
* for any <code>ThrowableSet</code> <code>s</code> and
* {@link soot.RefType RefType} <code>r</code>, and
*
* <blockquote>
* <code>t == p.getUncaught()</code> or
* <code>t == p.getCaught()</code>
* </blockquote>
*
* then calls to
* <code>t.add(r)</code>, <code>t.add(a)</code>, and <code>s.add(t)</code>,
* will throw an {@link ThrowableSet.AlreadyHasExclusionsException}, for any
* <code>RefType</code> <code>r</code>, {@link AnySubType} <code>a</code>,
* and <code>ThrowableSet</code> <code>t</code>.</p>
*
* <p> Actually the restrictions implemented are not quite so strict
* (there are some combinations of <code>whichCatchableAs()</code>
* followed by <code>add()</code> which will not raise an exception),
* but a more accurate description would require reference to the
* internals of the current implementation. The restrictions should
* not be too onerous for <code>ThrowableSet</code>'s anticipated
* uses: we expect <code>ThrowableSet</code>s to grow by accumulating
* all the exception types that a given {@link Unit} may throw, then,
* shrink as the types caught by different exception handlers are
* removed to yield the sets representing exceptions which escape
* those handlers.</p>
*
* <p> The <code>ThrowableSet</code> class is intended to be immutable
* (hence the <code>final</code> modifier on its declaration). It
* does not take the step of guaranteeing immutability by cloning the
* <code>RefLikeType</code> objects it contains, though, because we trust
* {@link Scene} to enforce the existence of only one
* <code>RefLikeType</code> instance with a given name.</p>
*/
public final class ThrowableSet {
private static final boolean INSTRUMENTING = true;
/**
* Singleton class for fields and initializers common to all
* ThrowableSet objects (i.e., these would be static fields and
* initializers, in the absence of soot's {@link G} and {@link
* Singletons} classes).
*/
public static class Manager {
/**
* Map from {@link Integer}s representing set size to all
* <code>ThrowableSet</code>s of that size.
*/
private final Map<Integer, List> sizeToSets = new HashMap<Integer, List>();
/**
* <code>ThrowableSet</code> containing no exception classes.
*/
public final ThrowableSet EMPTY;
/**
* <code>ThrowableSet</code> representing all possible
* Throwables.
*/
final ThrowableSet ALL_THROWABLES;
/**
* <code>ThrowableSet</code> containing all the asynchronous
* and virtual machine errors, which may be thrown by any
* bytecode instruction at any point in the computation.
*/
final ThrowableSet VM_ERRORS;
/**
* <code>ThrowableSet</code> containing all the exceptions
* that may be thrown in the course of resolving a reference
* to another class, including the process of loading, preparing,
* and verifying the referenced class.
*/
final ThrowableSet RESOLVE_CLASS_ERRORS;
/**
* <code>ThrowableSet</code> containing all the exceptions
* that may be thrown in the course of resolving a reference
* to a field.
*/
final ThrowableSet RESOLVE_FIELD_ERRORS;
/**
* <code>ThrowableSet</code> containing all the exceptions
* that may be thrown in the course of resolving a reference
* to a non-static method.
*/
final ThrowableSet RESOLVE_METHOD_ERRORS;
/**
* <code>ThrowableSet</code> containing all the exceptions
* which may be thrown by instructions that have the potential
* to cause a new class to be loaded and initialized (including
* UnsatisfiedLinkError, which is raised at runtime rather than
* linking type).
*/
final ThrowableSet INITIALIZATION_ERRORS;
final RefType RUNTIME_EXCEPTION;
final RefType ARITHMETIC_EXCEPTION;
final RefType ARRAY_STORE_EXCEPTION;
final RefType CLASS_CAST_EXCEPTION;
final RefType ILLEGAL_MONITOR_STATE_EXCEPTION;
final RefType INDEX_OUT_OF_BOUNDS_EXCEPTION;
final RefType ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION;
final RefType NEGATIVE_ARRAY_SIZE_EXCEPTION;
final RefType NULL_POINTER_EXCEPTION;
final RefType INSTANTIATION_ERROR;
// counts for instrumenting:
private int registeredSets = 0;
private int addsOfRefType = 0;
private int addsOfAnySubType = 0;
private int addsOfSet = 0;
private int addsInclusionFromMap = 0;
private int addsInclusionFromMemo = 0;
private int addsInclusionFromSearch = 0;
private int addsInclusionInterrupted = 0;
private int addsExclusionWithSearch = 0;
private int addsExclusionWithoutSearch = 0;
private int removesOfAnySubType = 0;
private final int removesFromMap = 0;
private final int removesFromMemo = 0;
private int removesFromSearch = 0;
private int registrationCalls = 0;
private int catchableAsQueries = 0;
private int catchableAsFromMap = 0;
private int catchableAsFromSearch = 0;
/**
* Constructs a <code>ThrowableSet.Manager</code> for inclusion in
* Soot's global variable manager, {@link G}.
*
* @param g guarantees that the constructor may only be called
* from {@link Singletons}.
*/
public Manager( Singletons.Global g ) {
// First ensure the Exception classes are represented in Soot.
// Runtime errors:
RUNTIME_EXCEPTION =
Scene.v().getRefType("java.lang.RuntimeException");
ARITHMETIC_EXCEPTION =
Scene.v().getRefType("java.lang.ArithmeticException");
ARRAY_STORE_EXCEPTION =
Scene.v().getRefType("java.lang.ArrayStoreException");
CLASS_CAST_EXCEPTION =
Scene.v().getRefType("java.lang.ClassCastException");
ILLEGAL_MONITOR_STATE_EXCEPTION =
Scene.v().getRefType("java.lang.IllegalMonitorStateException");
INDEX_OUT_OF_BOUNDS_EXCEPTION =
Scene.v().getRefType("java.lang.IndexOutOfBoundsException");
ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION =
Scene.v().getRefType("java.lang.ArrayIndexOutOfBoundsException");
NEGATIVE_ARRAY_SIZE_EXCEPTION =
Scene.v().getRefType("java.lang.NegativeArraySizeException");
NULL_POINTER_EXCEPTION =
Scene.v().getRefType("java.lang.NullPointerException");
INSTANTIATION_ERROR =
Scene.v().getRefType("java.lang.InstantiationError");
EMPTY = registerSetIfNew(null, null);
Set allThrowablesSet = new HashSet();
allThrowablesSet.add(AnySubType.v(Scene.v().getRefType("java.lang.Throwable")));
ALL_THROWABLES = registerSetIfNew(allThrowablesSet, null);
Set vmErrorSet = new HashSet();
vmErrorSet.add(Scene.v().getRefType("java.lang.InternalError"));
vmErrorSet.add(Scene.v().getRefType("java.lang.OutOfMemoryError"));
vmErrorSet.add(Scene.v().getRefType("java.lang.StackOverflowError"));
vmErrorSet.add(Scene.v().getRefType("java.lang.UnknownError"));
// The Java library's deprecated Thread.stop(Throwable) method
// would actually allow _any_ Throwable to be delivered
// asynchronously, not just java.lang.ThreadDeath.
vmErrorSet.add(Scene.v().getRefType("java.lang.ThreadDeath"));
VM_ERRORS = registerSetIfNew(vmErrorSet, null);
Set resolveClassErrorSet = new HashSet();
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.ClassCircularityError"));
// We add AnySubType(ClassFormatError) so that we can
// avoid adding its subclass,
// UnsupportedClassVersionError, explicitly. This is a
// hack to allow Soot to analyze older class libraries
// (UnsupportedClassVersionError was added in JDK 1.2).
if(!Options.v().j2me())
resolveClassErrorSet.add(AnySubType.v(Scene.v().getRefType("java.lang.ClassFormatError")));
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.IllegalAccessError"));
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.IncompatibleClassChangeError"));
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.LinkageError"));
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.NoClassDefFoundError"));
resolveClassErrorSet.add(Scene.v().getRefType("java.lang.VerifyError"));
RESOLVE_CLASS_ERRORS = registerSetIfNew(resolveClassErrorSet, null);
Set resolveFieldErrorSet = new HashSet(resolveClassErrorSet);
resolveFieldErrorSet.add(Scene.v().getRefType("java.lang.NoSuchFieldError"));
RESOLVE_FIELD_ERRORS = registerSetIfNew(resolveFieldErrorSet, null);
Set resolveMethodErrorSet = new HashSet(resolveClassErrorSet);
resolveMethodErrorSet.add(Scene.v().getRefType("java.lang.AbstractMethodError"));
resolveMethodErrorSet.add(Scene.v().getRefType("java.lang.NoSuchMethodError"));
resolveMethodErrorSet.add(Scene.v().getRefType("java.lang.UnsatisfiedLinkError"));
RESOLVE_METHOD_ERRORS = registerSetIfNew(resolveMethodErrorSet, null);
// The static initializers of a newly loaded class might
// throw any Error (if they threw an Exception---even a
// RuntimeException---it would be replaced by an
// ExceptionInInitializerError):
//
Set initializationErrorSet = new HashSet();
initializationErrorSet.add(AnySubType.v(Scene.v().getRefType("java.lang.Error")));
INITIALIZATION_ERRORS = registerSetIfNew(initializationErrorSet, null);
}
/**
* Returns the single instance of <code>ThrowableSet.Manager</code>.
*
* @return Soot's <code>ThrowableSet.Manager</code>.
*/
public static Manager v() {
return G.v().soot_toolkits_exceptions_ThrowableSet_Manager();
}
/**
* <p>Returns a <code>ThrowableSet</code> representing the set of
* exceptions included in <code>include</code> minus the set
* of exceptions included in <code>exclude</code>. Creates a
* new <code>ThrowableSet</code> only if there was not already
* one whose contents correspond to <code>include</code> -
* <code>exclude</code>.</p>
*
* @param include A set of {@link RefLikeType}
* objects representing exception types included in the result; may
* be <code>null</code> if there are no included types.
*
* @param exclude A set of {@link AnySubType}
* objects representing exception types excluded from the result; may
* be <code>null</code> if there are no excluded types.
*
* @return a <code>ThrowableSet</code> representing the set of
* exceptions corresponding to <code>include</code> -
* <code>exclude</code>.
*/
private ThrowableSet registerSetIfNew(Set include, Set exclude) {
if (INSTRUMENTING) {
registrationCalls++;
}
if (include == null) {
include = Collections.EMPTY_SET;
}
if (exclude == null) {
exclude = Collections.EMPTY_SET;
}
int size = include.size() + exclude.size();
Integer sizeKey = new Integer(size);
List<ThrowableSet> sizeList = sizeToSets.get(sizeKey);
if (sizeList == null) {
sizeList = new LinkedList<ThrowableSet>();
sizeToSets.put(sizeKey, sizeList);
}
for (ThrowableSet set : sizeList) {
if (set.exceptionsIncluded.equals(include)
&& set.exceptionsExcluded.equals(exclude)) {
return set;
}
}
if (INSTRUMENTING) {
registeredSets++;
}
ThrowableSet result = new ThrowableSet(include, exclude);
sizeList.add(result);
return result;
}
/**
* Report the counts collected by instrumentation (for now, at
* least, there is no need to provide access to the individual
* values as numbers).
*
* @return a string listing the counts.
*/
public String reportInstrumentation() {
int setCount = 0;
for (List sizeList : sizeToSets.values()) {
setCount += sizeList.size();
}
if (setCount != registeredSets) {
throw new IllegalStateException("ThrowableSet.reportInstrumentation() assertion failure: registeredSets != list count");
}
StringBuffer buf = new StringBuffer("registeredSets: ")
.append(setCount)
.append("\naddsOfRefType: ")
.append(addsOfRefType)
.append("\naddsOfAnySubType: ")
.append(addsOfAnySubType)
.append("\naddsOfSet: ")
.append(addsOfSet)
.append("\naddsInclusionFromMap: ")
.append(addsInclusionFromMap)
.append("\naddsInclusionFromMemo: ")
.append(addsInclusionFromMemo)
.append("\naddsInclusionFromSearch: ")
.append(addsInclusionFromSearch)
.append("\naddsInclusionInterrupted: ")
.append(addsInclusionInterrupted)
.append("\naddsExclusionWithoutSearch: ")
.append(addsExclusionWithoutSearch)
.append("\naddsExclusionWithSearch: ")
.append(addsExclusionWithSearch)
.append("\nremovesOfAnySubType: ")
.append(removesOfAnySubType)
.append("\nremovesFromMap: ")
.append(removesFromMap)
.append("\nremovesFromMemo: ")
.append(removesFromMemo)
.append("\nremovesFromSearch: ")
.append(removesFromSearch)
.append("\nregistrationCalls: ")
.append(registrationCalls)
.append("\ncatchableAsQueries: ")
.append(catchableAsQueries)
.append("\ncatchableAsFromMap: ")
.append(catchableAsFromMap)
.append("\ncatchableAsFromSearch: ")
.append(catchableAsFromSearch)
.append('\n');
return buf.toString();
}
/**
* A package-private method to provide unit tests with access
* to the collection of ThrowableSets.
*/
Map<Integer, List> getSizeToSets() {
return Manager.v().sizeToSets;
}
}
public static class AlreadyHasExclusionsException extends IllegalStateException {
public AlreadyHasExclusionsException(String s) {
super(s);
}
}
/**
* Set of exception types included within the set.
*/
private final Set exceptionsIncluded;
/**
* Set of exception types which, though members of
* exceptionsIncluded, are to be excluded from the types
* represented by this <code>ThrowableSet</code>. To simplify
* the implementation, once a <code>ThrowableSet</code> has
* any excluded types, the various <code>add()</code> methods of
* this class must bar additions of subtypes of those
* excluded types.
*/
private final Set exceptionsExcluded;
/**
* A map from
* ({@link RefLikeType} \\union <code>ThrowableSet</code>)
* to <code>ThrowableSet</code>. If the mapping (k,v) is in
* <code>memoizedAdds</code> and k is a
* <code>ThrowableSet</code>, then v is the set that
* results from adding all elements in k to <code>this</code>. If
* (k,v) is in <code>memoizedAdds</code> and k is a
* {@link RefLikeType}, then v is the set that results from adding
* k to <code>this</code>.
*/
private Map<Object,ThrowableSet> memoizedAdds;
private ThrowableSet getMemoizedAdds(Object key) {
if (memoizedAdds == null) {
memoizedAdds = new HashMap();
}
return memoizedAdds.get(key);
}
/**
* Constructs a <code>ThrowableSet</code> which contains the
* exception types represented in <code>include</code>, except for
* those which are also in <code>exclude</code>. The constructor
* is private to ensure that the only way to get a new
* <code>ThrowableSet</code> is by adding elements to or removing
* them from an existing set.
*
* @param include The set of {@link RefType} and {@link AnySubType}
* objects representing the types to be included in the set.
* @param exclude The set of {@link AnySubType}
* objects representing the types to be excluded
* from the set.
*/
private ThrowableSet(Set include, Set exclude) {
exceptionsIncluded = Collections.unmodifiableSet(include);
exceptionsExcluded = Collections.unmodifiableSet(exclude);
// We don't need to clone include and exclude to guarantee
// immutability since ThrowableSet(Set,Set) is private to this
// class, where it is only called (via
// Manager.v().registerSetIfNew()) with arguments which the
// callers do not subsequently modify.
}
/**
* Returns a <code>ThrowableSet</code> which contains
* <code>e</code> in addition to the exceptions in
* this <code>ThrowableSet</code>.
*
* <p>Add <code>e</code> as a {@link RefType} when
* you know that the run-time class of the exception you are representing is
* necessarily <code>e</code> and cannot be a subclass of
* <code>e</code>.
*
* <p>For example, if you were
* recording the type of the exception thrown by
*
* <pre>
* throw new IOException("Permission denied");
* </pre>
*
* you would call
*
* <pre>
* <code>add(Scene.v().getRefType("java.lang.Exception.IOException"))</code>
* </pre>
*
* since the class of the exception is necessarily
* <code>IOException</code>.
*
* @param e the exception class
*
* @return a set containing <code>e</code> as well as the
* exceptions in this set.
*
* @throws {@link ThrowableSet.IllegalStateException} if this
* <code>ThrowableSet</code> is the result of a {@link
* #whichCatchableAs(RefType)} operation and, thus, unable to
* represent the addition of <code>e</code>.
*/
public ThrowableSet add(RefType e)
throws ThrowableSet.AlreadyHasExclusionsException {
if (INSTRUMENTING) {
Manager.v().addsOfRefType++;
}
if (this.exceptionsIncluded.contains(e)) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromMap++;
Manager.v().addsExclusionWithoutSearch++;
}
return this;
} else {
ThrowableSet result = getMemoizedAdds(e);
if (result != null) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromMemo++;
Manager.v().addsExclusionWithoutSearch++;
}
return result;
} else {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromSearch++;
if (exceptionsExcluded.size() != 0) {
Manager.v().addsExclusionWithSearch++;
} else {
Manager.v().addsExclusionWithoutSearch++;
}
}
FastHierarchy hierarchy = Scene.v().getOrMakeFastHierarchy();
for (Iterator i = exceptionsExcluded.iterator(); i.hasNext(); ) {
RefType exclusionBase = ((AnySubType) i.next()).getBase();
if (hierarchy.canStoreType(e, exclusionBase)) {
throw new AlreadyHasExclusionsException(
"ThrowableSet.add(RefType): adding" + e.toString()
+ " to the set [ " + this.toString()
+ "] where " + exclusionBase.toString()
+ " is excluded.");
}
}
for (Iterator i = exceptionsIncluded.iterator(); i.hasNext() ; ) {
RefLikeType incumbent = (RefLikeType) i.next();
if (incumbent instanceof AnySubType) {
// Need to use incumbent.getBase() because
// hierarchy.canStoreType() assumes that parent
// is not an AnySubType.
RefType incumbentBase = ((AnySubType) incumbent).getBase();
if (hierarchy.canStoreType(e, incumbentBase)) {
memoizedAdds.put(e, this);
return this;
}
} else if (! (incumbent instanceof RefType)) {
// assertion failure.
throw new IllegalStateException("ThrowableSet.add(RefType): Set element " +
incumbent.toString() +
" is neither a RefType nor an AnySubType.");
}
}
Set resultSet = new HashSet(this.exceptionsIncluded);
resultSet.add(e);
result = Manager.v().registerSetIfNew(resultSet,
this.exceptionsExcluded);
memoizedAdds.put(e, result);
return result;
}
}
}
/**
* Returns a <code>ThrowableSet</code> which contains
* <code>e</code> and all of its subclasses as well as the
* exceptions in this set.
*
* <p><code>e</code> should be an instance of {@link AnySubType}
* if you know that the
* compile-time type of the exception you are representing is
* <code>e</code>, but the exception may be instantiated at run-time
* by a subclass of
* <code>e</code>.
*
* <p>For example, if you were recording the type of
* the exception thrown by
*
* <pre>
* catch (IOException e) {
* throw e;
* }
* </pre>
*
* you would call
*
* <pre>
* <code>add(AnySubtype.v(Scene.v().getRefType("java.lang.Exception.IOException")))</code>
* </pre>
*
* since the handler might rethrow any subclass of
* <code>IOException</code>.
*
* @param e represents a subtree of the exception class hierarchy
* to add to this set.
*
* @return a set containing <code>e</code> and all its subclasses,
* as well as the exceptions represented by this set.
*
* @throws ThrowableSet.AlreadyHasExclusionsException if this
* <code>ThrowableSet</code> is the result of a {@link
* #whichCatchableAs(RefType)} operation and, thus, unable to
* represent the addition of <code>e</code>.
*/
public ThrowableSet add(AnySubType e)
throws ThrowableSet.AlreadyHasExclusionsException {
if (INSTRUMENTING) {
Manager.v().addsOfAnySubType++;
}
ThrowableSet result = getMemoizedAdds(e);
if (result != null) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromMemo++;
Manager.v().addsExclusionWithoutSearch++;
}
return result;
} else {
FastHierarchy hierarchy = Scene.v().getOrMakeFastHierarchy();
RefType newBase = e.getBase();
if (INSTRUMENTING) {
if (exceptionsExcluded.size() != 0) {
Manager.v().addsExclusionWithSearch++;
} else {
Manager.v().addsExclusionWithoutSearch++;
}
}
for (Iterator i = exceptionsExcluded.iterator(); i.hasNext(); ) {
RefType exclusionBase = ((AnySubType) i.next()).getBase();
if (hierarchy.canStoreType(newBase, exclusionBase)
|| hierarchy.canStoreType(exclusionBase, newBase)) {
if (INSTRUMENTING) {
// To ensure that the subcategories total properly:
Manager.v().addsInclusionInterrupted++;
}
throw new AlreadyHasExclusionsException(
"ThrowableSet.add(" + e.toString()
+ ") to the set [ " + this.toString()
+ "] where " + exclusionBase.toString()
+ " is excluded.");
}
}
if (this.exceptionsIncluded.contains(e)) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromMap++;
}
return this;
} else {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromSearch++;
}
int changes = 0;
boolean addNewException = true;
Set resultSet = new HashSet();
for (Iterator i = this.exceptionsIncluded.iterator(); i.hasNext() ; ) {
RefLikeType incumbent = (RefLikeType) i.next();
if (incumbent instanceof RefType) {
if (hierarchy.canStoreType(incumbent, newBase)) {
// Omit incumbent from result.
changes++;
} else {
resultSet.add(incumbent);
}
} else if (incumbent instanceof AnySubType) {
RefType incumbentBase = ((AnySubType) incumbent).getBase();
// We have to use the base types in these hierarchy calls
// because we want to know if _all_ possible
// types represented by e can be represented by
// the incumbent, or vice versa.
if (hierarchy.canStoreType(newBase, incumbentBase)) {
addNewException = false;
resultSet.add(incumbent);
} else if (hierarchy.canStoreType(incumbentBase, newBase)) {
// Omit incumbent from result;
changes++;
} else {
resultSet.add(incumbent);
}
} else { // assertion failure.
throw new IllegalStateException("ThrowableSet.add(AnySubType): Set element " +
incumbent.toString() +
" is neither a RefType nor an AnySubType.");
}
}
if (addNewException) {
resultSet.add(e);
changes++;
}
if (changes > 0) {
result = Manager.v().registerSetIfNew(resultSet,
this.exceptionsExcluded);
} else {
result = this;
}
memoizedAdds.put(e, result);
return result;
}
}
}
/**
* Returns a <code>ThrowableSet</code> which contains
* all the exceptions in <code>s</code> in addition to those in
* this <code>ThrowableSet</code>.
*
* @param s set of exceptions to add to this set.
*
* @return the union of this set with <code>s</code>
*
* @throws ThrowableSet.AlreadyHasExclusionsException if this
* <code>ThrowableSet</code> or <code>s</code> is the
* result of a {@link #whichCatchableAs(RefType)} operation, so that
* it is not possible to represent the addition of <code>s</code> to
* this <code>ThrowableSet</code>.
*/
public ThrowableSet add(ThrowableSet s)
throws ThrowableSet.AlreadyHasExclusionsException {
if (INSTRUMENTING) {
Manager.v().addsOfSet++;
}
if (exceptionsExcluded.size() > 0 || s.exceptionsExcluded.size() > 0) {
throw new AlreadyHasExclusionsException("ThrowableSet.Add(ThrowableSet): attempt to add to [" + this.toString() + "] after removals recorded.");
}
ThrowableSet result = getMemoizedAdds(s);
if (result == null) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromSearch++;
Manager.v().addsExclusionWithoutSearch++;
}
result = this.add(s.exceptionsIncluded);
memoizedAdds.put(s, result);
} else if (INSTRUMENTING) {
Manager.v().addsInclusionFromMemo++;
Manager.v().addsExclusionWithoutSearch++;
}
return result;
}
/**
* Returns a <code>ThrowableSet</code> which contains all
* the exceptions in <code>addedExceptions</code> in addition to those
* in this <code>ThrowableSet</code>.
*
* @param addedExceptions a set of {@link RefLikeType} and
* {@link AnySubType} objects to be added to the types included in this
* <code>ThrowableSet</code>.
*
* @return a set containing all the <code>addedExceptions</code> as well
* as the exceptions in this set.
*/
private ThrowableSet add(Set addedExceptions) {
Set resultSet = new HashSet(this.exceptionsIncluded);
int changes = 0;
FastHierarchy hierarchy = Scene.v().getOrMakeFastHierarchy();
// This algorithm is O(n m), where n and m are the sizes of the
// two sets, so hope that the sets are small.
for (Iterator i = addedExceptions.iterator(); i.hasNext(); ) {
RefLikeType newType = (RefLikeType) i.next();
if (! resultSet.contains(newType)) {
boolean addNewType = true;
if (newType instanceof RefType) {
for (Iterator j = resultSet.iterator(); j.hasNext(); ) {
RefLikeType incumbentType = (RefLikeType) j.next();
if (incumbentType instanceof RefType) {
if (newType == incumbentType) {
// assertion failure.
throw new IllegalStateException("ThrowableSet.add(Set): resultSet.contains() failed to screen duplicate RefType "
+ newType);
}
} else if (incumbentType instanceof AnySubType) {
RefType incumbentBase = ((AnySubType) incumbentType).getBase();
if (hierarchy.canStoreType(newType, incumbentBase)) {
// No need to add this class.
addNewType = false;
}
} else { // assertion failure.
throw new IllegalStateException("ThrowableSet.add(Set): incumbent Set element "
+ incumbentType
+ " is neither a RefType nor an AnySubType.");
}
}
} else if (newType instanceof AnySubType) {
RefType newBase = ((AnySubType) newType).getBase();
for (Iterator j = resultSet.iterator(); j.hasNext(); ) {
RefLikeType incumbentType = (RefLikeType) j.next();
if (incumbentType instanceof RefType) {
RefType incumbentBase = (RefType) incumbentType;
if (hierarchy.canStoreType(incumbentBase, newBase)) {
j.remove();
changes++;
}
} else if (incumbentType instanceof AnySubType) {
RefType incumbentBase = ((AnySubType) incumbentType).getBase();
if (newBase == incumbentBase) {
// assertion failure.
throw new IllegalStateException("ThrowableSet.add(Set): resultSet.contains() failed to screen duplicate AnySubType "
+ newBase);
} else if (hierarchy.canStoreType(incumbentBase, newBase)) {
j.remove();
changes++;
} else if (hierarchy.canStoreType(newBase, incumbentBase)) {
// No need to add this class.
addNewType = false;
}
} else { // assertion failure.
throw new IllegalStateException("ThrowableSet.add(Set): old Set element "
+ incumbentType
+ " is neither a RefType nor an AnySubType.");
}
}
} else { // assertion failure.
throw new IllegalArgumentException("ThrowableSet.add(Set): new Set element "
+ newType
+ " is neither a RefType nor an AnySubType.");
}
if (addNewType) {
changes++;
resultSet.add(newType);
}
}
}
ThrowableSet result = null;
if (changes > 0) {
result = Manager.v().registerSetIfNew(resultSet,
this.exceptionsExcluded);
} else {
result = this;
}
return result;
}
/**
* Indicates whether this ThrowableSet includes some
* exception that might be caught by a handler argument of the
* type <code>catcher</code>.
*
* @param catcher type of the handler parameter to be tested.
*
* @return <code>true</code> if this set contains an exception type
* that might be caught by <code>catcher</code>,
* false if it does not.
*/
public boolean catchableAs(RefType catcher) {
if (INSTRUMENTING) {
Manager.v().catchableAsQueries++;
}
FastHierarchy h = Scene.v().getOrMakeFastHierarchy();
if (exceptionsExcluded.size() > 0) {
if (INSTRUMENTING) {
Manager.v().catchableAsFromSearch++;
}
for (Iterator i = exceptionsExcluded.iterator(); i.hasNext(); ) {
AnySubType exclusion = (AnySubType) i.next();
if (h.canStoreType(catcher, exclusion.getBase())) {
return false;
}
}
}
if (exceptionsIncluded.contains(catcher)) {
if (INSTRUMENTING) {
if (exceptionsExcluded.size() == 0) {
Manager.v().catchableAsFromMap++;
} else {
Manager.v().catchableAsFromSearch++;
}
}
return true;
} else {
if (INSTRUMENTING) {
if (exceptionsExcluded.size() == 0) {
Manager.v().catchableAsFromSearch++;
}
}
for (Iterator i = exceptionsIncluded.iterator(); i.hasNext(); ) {
RefLikeType thrownType = (RefLikeType) i.next();
if (thrownType instanceof RefType) {
if (thrownType == catcher) {
// assertion failure.
throw new IllegalStateException("ThrowableSet.catchableAs(RefType): exceptions.contains() failed to match contained RefType "
+ catcher);
} else if (h.canStoreType(thrownType, catcher)) {
return true;
}
} else {
RefType thrownBase = ((AnySubType) thrownType).getBase();
// At runtime, thrownType might be instantiated by any
// of thrownBase's subtypes, so:
if (h.canStoreType(thrownBase, catcher)
|| h.canStoreType(catcher, thrownBase)) {
return true;
}
}
}
return false;
}
}
/**
* Partitions the exceptions in this <code>ThrowableSet</code>
* into those which would be caught by a handler with the passed
* <code>catch</code> parameter type and those which would not.
*
* @param catcher type of the handler parameter to be tested.
*
* @return a pair of <code>ThrowableSet</code>s, one containing the
* types in this <code>ThrowableSet</code> which would be
* be caught as <code>catcher</code> and the other containing
* the types in this <code>ThrowableSet</code> which would
* not be caught as <code>catcher</code>.
*/
public Pair whichCatchableAs(RefType catcher) {
if (INSTRUMENTING) {
Manager.v().removesOfAnySubType++;
}
FastHierarchy h = Scene.v().getOrMakeFastHierarchy();
Set caughtIncluded = null;
Set caughtExcluded = null;
Set uncaughtIncluded = null;
Set uncaughtExcluded = null;
if (INSTRUMENTING) {
Manager.v().removesFromSearch++;
}
for (Iterator i = exceptionsExcluded.iterator(); i.hasNext(); ) {
AnySubType exclusion = (AnySubType) i.next();
RefType exclusionBase = exclusion.getBase();
if (h.canStoreType(catcher, exclusionBase)) {
// Because the add() operations ban additions to sets
// with exclusions, we can be sure no types in this are
// caught by catcher.
return new Pair(ThrowableSet.Manager.v().EMPTY, this);
} else if (h.canStoreType(exclusionBase, catcher)) {
// exclusion wouldn't be in exceptionsExcluded if one
// of its supertypes were not in exceptionsIncluded,
// so we know the next loop will add either that supertype
// or catcher to caughtIncluded. Thus:
caughtExcluded = addExceptionToSet(exclusion, caughtExcluded);
} else {
uncaughtExcluded = addExceptionToSet(exclusion, uncaughtExcluded);
}
}
for (Iterator i = exceptionsIncluded.iterator(); i.hasNext(); ) {
RefLikeType inclusion = (RefLikeType) i.next();
if (inclusion instanceof RefType) {
if (h.canStoreType(inclusion, catcher)) {
caughtIncluded = addExceptionToSet(inclusion, caughtIncluded);
} else {
uncaughtIncluded = addExceptionToSet(inclusion, uncaughtIncluded);
}
} else {
RefType base = ((AnySubType) inclusion).getBase();
if (h.canStoreType(base, catcher)) {
// All subtypes of base will be caught. Any exclusions
// will already have been copied to caughtExcluded by
// the preceding loop.
caughtIncluded =
addExceptionToSet(inclusion, caughtIncluded);
} else if (h.canStoreType(catcher, base)) {
// Some subtypes of base will be caught, and
// we know that not all of those catchable subtypes
// are among exceptionsExcluded, since in that case we
// would already have returned from within the
// preceding loop. So, remove AnySubType(catcher)
// from the uncaught types.
uncaughtIncluded
= addExceptionToSet(inclusion, uncaughtIncluded);
uncaughtExcluded
= addExceptionToSet(AnySubType.v(catcher), uncaughtExcluded);
caughtIncluded
= addExceptionToSet(AnySubType.v(catcher), caughtIncluded);
// Any already excluded subtypes of inclusion
// which are subtypes of catcher will have been
// added to caughtExcluded by the previous loop.
} else {
uncaughtIncluded
= addExceptionToSet(inclusion, uncaughtIncluded);
}
}
}
ThrowableSet caughtSet
= Manager.v().registerSetIfNew(caughtIncluded, caughtExcluded);
ThrowableSet uncaughtSet
= Manager.v().registerSetIfNew(uncaughtIncluded, uncaughtExcluded);
return new Pair(caughtSet, uncaughtSet);
}
/**
* The return type for {@link ThrowableSet#whichCatchableAs(RefType)},
* consisting of a pair of ThrowableSets.
*/
public static class Pair {
private ThrowableSet caught;
private ThrowableSet uncaught;
/**
* Constructs a <code>ThrowableSet.Pair</code>.
*
* @param caught The set of exceptions to be returned when
* {@link #getCaught()} is called on the constructed
* <code>ThrowableSet.Pair</code>.
*
* @param uncaught The set of exceptions to be returned when
* {@link #getUncaught()} is called on the
* constructed <code>ThrowableSet.Pair</code>.
*/
protected Pair(ThrowableSet caught, ThrowableSet uncaught) {
this.caught = caught;
this.uncaught = uncaught;
}
/**
* @return the set of caught exceptions.
*/
public ThrowableSet getCaught() {
return caught;
}
/**
* @return the set of uncaught exceptions.
*/
public ThrowableSet getUncaught() {
return uncaught;
}
/**
* Indicates whether two {@link Object}s are
* <code>ThrowableSet.Pair</code>s representing the same set of
* caught and uncaught exception types.
*
* @param o the <code>Object</code> to compare to this
* <code>ThrowableSet.Pair</code>.
*
* @return <code>true</code> if <code>o</code> is a
* <code>ThrowableSet.Pair</code> representing the same set of
* caught and uncaught types as this
* <code>ThrowableSet.Pair</code>.
*/
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (! (o instanceof Pair)) {
return false;
}
Pair tsp = (Pair) o;
if ( this.caught.equals(tsp.caught)
&& this.uncaught.equals(tsp.uncaught)) {
return true;
}
return false;
}
public int hashCode() {
int result = 31;
result = 37 * result + caught.hashCode();
result = 37 * result + uncaught.hashCode();
return result;
}
}
/**
* Utility method for building sets of exceptional types for a
* {@link Pair}.
*
* @param e The exceptional type to add to the set.
*
* @param set The <code>Set</code> to which to add the types, or
* <code>null</code> if no <code>Set</code> has yet been
* allocated.
*
* @return A <code>Set</code> containing the elements in <code>set</code>
* plus <code>e</code>.
*/
private Set addExceptionToSet(RefLikeType e, Set set) {
if (set == null) {
set = new HashSet();
}
set.add(e);
return set;
}
/**
* Returns a string representation of this <code>ThrowableSet</code>.
*/
public String toString() {
StringBuffer buffer = new StringBuffer(this.toBriefString());
buffer.append(":\n ");
for (Iterator i = exceptionsIncluded.iterator(); i.hasNext(); ) {
buffer.append('+');
Object o = i.next();
buffer.append(o == null ? "null" : o.toString());
// buffer.append(i.next().toString());
}
for (Iterator i = exceptionsExcluded.iterator(); i.hasNext(); ) {
buffer.append('-');
buffer.append(i.next().toString());
}
return buffer.toString();
}
/**
* Returns a cryptic identifier for this <code>ThrowableSet</code>,
* used to identify a set when it appears in a collection.
*/
public String toBriefString() {
return super.toString();
}
/**
* Returns an {@link Iterator} over a {@link Collection} of
* Throwable types which iterates over its elements in a
* consistent order (maintaining an ordering that is consistent
* across different runs makes it easier to compare sets generated
* by different implementations of the CFG classes).
*
* @param coll The collection to iterate over.
*
* @return An iterator which presents the elements of <code>coll</code>
* in order.
*/
private static Iterator sortedThrowableIterator(Collection coll) {
if (coll.size() <= 1) {
return coll.iterator();
} else {
Object array[] = coll.toArray();
Arrays.sort(array, new ThrowableComparator());
return Arrays.asList(array).iterator();
}
}
/**
* Comparator used to implement sortedThrowableIterator().
*
*/
private static class ThrowableComparator implements java.util.Comparator {
private static RefType baseType(Object o) {
if (o instanceof AnySubType) {
return ((AnySubType) o).getBase();
} else {
return (RefType) o; // ClassCastException if o is not a RefType.
}
}
public int compare(Object o1, Object o2) {
RefType t1 = baseType(o1);
RefType t2 = baseType(o2);
if (t1.equals(t2)) {
// There should never be both AnySubType(t) and
// t in a ThrowableSet, but if it happens, put
// AnySubType(t) first:
if (o1 instanceof AnySubType) {
if (o2 instanceof AnySubType) {
return 0;
} else {
return -1;
}
} else if (o2 instanceof AnySubType) {
return 1;
} else {
return 0;
}
} else {
return t1.toString().compareTo(t2.toString());
}
}
public boolean equal(Object o1, Object o2) {
return (o1.equals(o2));
}
}
/**
* <p>Produce an abbreviated representation of this
* <code>ThrowableSet</code>, suitable for human consumption. The
* abbreviations include:</p>
*
* <ul>
*
* <li>The strings “<code>java.lang.</code>” is
* stripped from the beginning of exception names.</li>
*
* <li>The string “<code>Exception</code>” is stripped from
* the ends of exception names.</li>
*
* <li>Instances of <code>AnySubType</code> are indicated by surrounding
* the base type name with parentheses, rather than with the string
* “<code>Any_subtype_of_</code>”</li>
*
* <li>If this <code>ThrowableSet</code> includes all the elements
* of {@link ThrowableSet.Manager#VM_ERRORS VM_ERRORS}, they are
* abbreviated as “<code>vmErrors</code>” rather than
* listed individually.</li>
*
* @return An abbreviated representation of the contents of this set.
*/
public String toAbbreviatedString() {
return toAbbreviatedString(exceptionsIncluded, '+')
+ toAbbreviatedString(exceptionsExcluded, '-');
}
/**
* <p>Utility method which prints the abbreviations of the
* elements in a passed {@link Set} of exception types.</p>
*
* @param s The exceptions to print.
*
* @param connector The character to insert between exceptions.
*
* @return An abbreviated representation of the exceptions.
*/
private String toAbbreviatedString(Set s, char connector) {
final String JAVA_LANG = "java.lang.";
final int JAVA_LANG_LENGTH = JAVA_LANG.length();
final String EXCEPTION = "Exception";
final int EXCEPTION_LENGTH = EXCEPTION.length();
Collection vmErrorThrowables = ThrowableSet.Manager.v().VM_ERRORS.exceptionsIncluded;
boolean containsAllVmErrors = s.containsAll(vmErrorThrowables);
StringBuffer buf = new StringBuffer();
if (containsAllVmErrors) {
buf.append(connector);
buf.append("vmErrors");
}
for (Iterator it = sortedThrowableIterator(s); it.hasNext(); ) {
RefLikeType reflikeType = (RefLikeType) it.next();
RefType baseType = null;
if (reflikeType instanceof RefType) {
baseType = (RefType)reflikeType;
if (vmErrorThrowables.contains(baseType) && containsAllVmErrors) {
continue; // Already accounted for vmErrors.
} else {
buf.append(connector);
}
} else if (reflikeType instanceof AnySubType) {
buf.append(connector);
buf.append('(');
baseType = ((AnySubType)reflikeType).getBase();
}
String typeName = baseType.toString();
if (typeName.startsWith(JAVA_LANG)) {
typeName = typeName.substring(JAVA_LANG_LENGTH);
}
if (typeName.length() > EXCEPTION_LENGTH &&
typeName.endsWith(EXCEPTION)) {
typeName = typeName.substring(0, typeName.length()-EXCEPTION_LENGTH);
}
buf.append(typeName);
if (reflikeType instanceof AnySubType) {
buf.append(')');
}
}
return buf.toString();
}
/**
* A package-private method to provide unit tests with access to
* the {@link RefLikeType} objects which represent the
* <code>Throwable</code> types included in this set.
*
* @return an unmodifiable collection view of the
* <code>Throwable</code> types in this set.
*/
Collection<Object> typesIncluded() {
return new AbstractCollection() {
public Iterator iterator() {
return new Iterator() {
private final Iterator i = exceptionsIncluded.iterator();
public boolean hasNext() {
return i.hasNext();
}
public Object next() {
return i.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
return exceptionsIncluded.size();
}
};
}
/**
* A package-private method to provide unit tests with access to
* the {@link RefLikeType} objects which represent the
* <code>Throwable</code> types excluded from this set.
*
* @return an unmodifiable collection view of the
* <code>Throwable</code> types excluded from this set.
*/
Collection<Object> typesExcluded() {
return new AbstractCollection() {
public Iterator iterator() {
return new Iterator() {
private final Iterator i = exceptionsExcluded.iterator();
public boolean hasNext() {
return i.hasNext();
}
public Object next() {
return i.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
return exceptionsExcluded.size();
}
};
}
/**
* A package-private method to provide unit tests with access to
* ThrowableSet's internals.
*/
Map getMemoizedAdds() {
if (memoizedAdds == null) {
return Collections.EMPTY_MAP;
} else {
return Collections.unmodifiableMap(memoizedAdds);
}
}
}