Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
soot.toolkits.exceptions.ThrowableSet Maven / Gradle / Ivy
package soot.toolkits.exceptions;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 2003 John Jorgensen
* %%
* This program 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 program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.common.cache.CacheBuilder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import soot.AnySubType;
import soot.FastHierarchy;
import soot.G;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.Singletons;
import soot.SootClass;
import soot.Unit;
import soot.options.Options;
/**
*
* A class for representing the set of exceptions that an instruction may throw.
*
*
*
* ThrowableSet
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.
*
*
*
* There is a limitation on the combinations of operations permitted on a ThrowableSet
. The
* ThrowableSet
s returned by {@link #whichCatchableAs(RefType)} cannot be involved in subsequent
* add()
or whichCatchableAs()
operations. That is, given
*
*
p = s.whichCatchableAs(r)
*
* for any ThrowableSet
s
and {@link soot.RefType RefType} r
, and
*
* t == p.getUncaught()
or t == p.getCaught()
*
* then calls to t.add(r)
, t.add(a)
, and s.add(t)
, will throw an
* {@link ThrowableSet.AlreadyHasExclusionsException}, for any RefType
r
, {@link AnySubType}
* a
, and ThrowableSet
t
.
*
*
*
* Actually the restrictions implemented are not quite so strict (there are some combinations of
* whichCatchableAs()
followed by add()
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 ThrowableSet
's anticipated uses: we expect ThrowableSet
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.
*
*
*
* The ThrowableSet
class is intended to be immutable (hence the final
modifier on its
* declaration). It does not take the step of guaranteeing immutability by cloning the RefLikeType
objects it
* contains, though, because we trust {@link Scene} to enforce the existence of only one RefLikeType
instance
* with a given name.
*
*/
public class ThrowableSet {
private static final boolean INSTRUMENTING = false;
private final static SootClass JAVA_LANG_OBJECT_CLASS = Scene.v().getObjectType().getSootClass();
/**
* Set of exception types included within the set.
*/
protected final Set exceptionsIncluded;
/**
* Set of exception types which, though members of exceptionsIncluded, are to be excluded from the types represented by
* this ThrowableSet
. To simplify the implementation, once a ThrowableSet
has any excluded types,
* the various add()
methods of this class must bar additions of subtypes of those excluded types.
*/
protected final Set exceptionsExcluded;
/**
* A map from ({@link RefLikeType} \\union ThrowableSet
) to ThrowableSet
. If the mapping (k,v) is
* in memoizedAdds
and k is a ThrowableSet
, then v is the set that results from adding all
* elements in k to this
. If (k,v) is in memoizedAdds
and k is a {@link RefLikeType}, then v is
* the set that results from adding k to this
.
*/
protected Map memoizedAdds;
/**
* Constructs a ThrowableSet
which contains the exception types represented in include
, except
* for those which are also in exclude
. The constructor is private to ensure that the only way to get a new
* ThrowableSet
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.
*/
protected ThrowableSet(Set include, Set exclude) {
exceptionsIncluded = getImmutable(include);
exceptionsExcluded = getImmutable(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.
}
private static Set getImmutable(Set in) {
if ((null == in) || in.isEmpty()) {
return Collections.emptySet();
}
if (1 == in.size()) {
return Collections.singleton(in.iterator().next());
}
return Collections.unmodifiableSet(in);
}
/**
* 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 coll
in order.
*/
private static Iterator sortedThrowableIterator(Collection coll) {
if (coll.size() <= 1) {
return coll.iterator();
} else {
@SuppressWarnings("unchecked")
T[] array = (T[]) coll.toArray(new RefLikeType[coll.size()]);
Arrays.sort(array, new ThrowableComparator());
return Arrays.asList(array).iterator();
}
}
private ThrowableSet getMemoizedAdds(Object key) {
return memoizedAdds == null ? null : memoizedAdds.get(key);
}
private void addToMemoizedAdds(Object key, ThrowableSet value) {
if (memoizedAdds == null) {
memoizedAdds = new HashMap<>();
}
memoizedAdds.put(key, value);
}
/**
* Returns a ThrowableSet
which contains e
in addition to the exceptions in this
* ThrowableSet
.
*
*
* Add e
as a {@link RefType} when you know that the run-time class of the exception you are representing is
* necessarily e
and cannot be a subclass of e
.
*
*
* For example, if you were recording the type of the exception thrown by
*
*
* throw new IOException("Permission denied");
*
*
* you would call
*
*
* add(Scene.v().getRefType("java.lang.Exception.IOException"))
*
*
* since the class of the exception is necessarily IOException
.
*
* @param e
* the exception class
*
* @return a set containing e
as well as the exceptions in this set.
*
* @throws {@link
* ThrowableSet.IllegalStateException} if this ThrowableSet
is the result of a
* {@link #whichCatchableAs(RefType)} operation and, thus, unable to represent the addition of e
.
*/
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;
}
ThrowableSet result = getMemoizedAdds(e);
if (result != null) {
if (INSTRUMENTING) {
Manager.v().addsInclusionFromMemo++;
Manager.v().addsExclusionWithoutSearch++;
}
return result;
}
if (INSTRUMENTING) {
Manager.v().addsInclusionFromSearch++;
if (exceptionsExcluded.isEmpty()) {
Manager.v().addsExclusionWithoutSearch++;
} else {
Manager.v().addsExclusionWithSearch++;
}
}
FastHierarchy hierarchy = Scene.v().getOrMakeFastHierarchy();
boolean eHasNoHierarchy = hasNoHierarchy(e);
for (AnySubType excludedType : exceptionsExcluded) {
RefType exclusionBase = excludedType.getBase();
if ((eHasNoHierarchy && exclusionBase.equals(e)) || (!eHasNoHierarchy && hierarchy.canStoreType(e, exclusionBase))) {
throw new AlreadyHasExclusionsException("ThrowableSet.add(RefType): adding" + e.toString() + " to the set [ "
+ this.toString() + "] where " + exclusionBase.toString() + " is excluded.");
}
}
// If this is a real class, we need to check whether we already have it
// in the list through subtyping.
if (!eHasNoHierarchy) {
for (RefLikeType incumbent : exceptionsIncluded) {
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)) {
addToMemoizedAdds(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);
addToMemoizedAdds(e, result);
return result;
}
private boolean hasNoHierarchy(RefType type) {
final SootClass sootClass = type.getSootClass();
return !(sootClass.hasSuperclass() || JAVA_LANG_OBJECT_CLASS == sootClass);
}
/**
* Returns a ThrowableSet
which contains e
and all of its subclasses as well as the exceptions in
* this set.
*
*
* e
should be an instance of {@link AnySubType} if you know that the compile-time type of the exception you
* are representing is e
, but the exception may be instantiated at run-time by a subclass of e
.
*
*
* For example, if you were recording the type of the exception thrown by
*
*
* catch (IOException e) {
* throw e;
* }
*
*
* you would call
*
*
* add(AnySubtype.v(Scene.v().getRefType("java.lang.Exception.IOException")))
*
*
* since the handler might rethrow any subclass of IOException
.
*
* @param e
* represents a subtree of the exception class hierarchy to add to this set.
*
* @return a set containing e
and all its subclasses, as well as the exceptions represented by this set.
*
* @throws ThrowableSet.AlreadyHasExclusionsException
* if this ThrowableSet
is the result of a {@link #whichCatchableAs(RefType)} operation and, thus,
* unable to represent the addition of e
.
*/
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;
}
// java.lang.Object is managed by the Scene -> guaranteed to only have one instance of the Object class
final SootClass objectClass = Scene.v().getObjectType().getSootClass();
FastHierarchy hierarchy = Scene.v().getOrMakeFastHierarchy();
RefType newBase = e.getBase();
boolean newBaseHasNoHierarchy = hasNoHierarchy(newBase);
if (INSTRUMENTING) {
if (exceptionsExcluded.isEmpty()) {
Manager.v().addsExclusionWithoutSearch++;
} else {
Manager.v().addsExclusionWithSearch++;
}
}
for (AnySubType excludedType : exceptionsExcluded) {
RefType exclusionBase = excludedType.getBase();
boolean exclusionBaseHasNoHierarchy = !(exclusionBase.getSootClass().hasSuperclass() || //
exclusionBase.getSootClass() == objectClass);
boolean isExcluded = exclusionBaseHasNoHierarchy && exclusionBase.equals(newBase);
isExcluded |= !exclusionBaseHasNoHierarchy
&& (hierarchy.canStoreType(newBase, exclusionBase) || hierarchy.canStoreType(exclusionBase, newBase));
if (isExcluded) {
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;
}
if (INSTRUMENTING) {
Manager.v().addsInclusionFromSearch++;
}
int changes = 0;
boolean addNewException = true;
Set resultSet = new HashSet<>();
for (RefLikeType incumbent : this.exceptionsIncluded) {
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();
if (newBaseHasNoHierarchy) {
if (!incumbentBase.equals(newBase)) {
resultSet.add(incumbent);
}
}
// 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.
else 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;
}
addToMemoizedAdds(e, result);
return result;
}
/**
* Returns a ThrowableSet
which contains all the exceptions in s
in addition to those in this
* ThrowableSet
.
*
* @param s
* set of exceptions to add to this set.
*
* @return the union of this set with s
*
* @throws ThrowableSet.AlreadyHasExclusionsException
* if this ThrowableSet
or s
is the result of a {@link #whichCatchableAs(RefType)}
* operation, so that it is not possible to represent the addition of s
to this
* ThrowableSet
.
*/
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);
addToMemoizedAdds(s, result);
} else if (INSTRUMENTING) {
Manager.v().addsInclusionFromMemo++;
Manager.v().addsExclusionWithoutSearch++;
}
return result;
}
/**
* Returns a ThrowableSet
which contains all the exceptions in addedExceptions
in addition to
* those in this ThrowableSet
.
*
* @param addedExceptions
* a set of {@link RefLikeType} and {@link AnySubType} objects to be added to the types included in this
* ThrowableSet
.
*
* @return a set containing all the addedExceptions
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 (RefLikeType newType : addedExceptions) {
if (!resultSet.contains(newType)) {
boolean addNewType = true;
if (newType instanceof RefType) {
for (RefLikeType incumbentType : resultSet) {
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 = 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;
}
/**
* Returns a ThrowableSet
which contains all the exceptions in this ThrowableSet
except for those
* in removedExceptions
.
*
* @param removedExceptions
* a set of {@link RefLikeType} and {@link AnySubType} objects to be added to the types included in this
* ThrowableSet
.
*
* @return a set containing all the addedExceptions
as well as the exceptions in this set.
*/
private ThrowableSet remove(Set removedExceptions) {
// Is there anything to remove?
if (removedExceptions.isEmpty()) {
return this;
}
int changes = 0;
Set resultSet = new HashSet<>(this.exceptionsIncluded);
for (RefLikeType tp : removedExceptions) {
if (tp instanceof RefType) {
if (resultSet.remove(tp)) {
changes++;
}
}
}
ThrowableSet result = null;
if (changes > 0) {
result = Manager.v().registerSetIfNew(resultSet, this.exceptionsExcluded);
} else {
result = this;
}
return result;
}
/**
* Returns a ThrowableSet
which contains all the exceptions from the current set except for those in the given
* ThrowableSet
.
*
* @param s
* The set containing the exceptions to exclude from the new set
*
* @return The exceptions that are only in this set, but not in the given set
*
* @throws ThrowableSet.AlreadyHasExclusionsException
* if this ThrowableSet
or s
is the result of a {@link #whichCatchableAs(RefType)}
* operation, so that it is not possible to represent the addition of s
to this
* ThrowableSet
.
*/
public ThrowableSet remove(ThrowableSet s) {
if ((exceptionsExcluded.size() > 0) || (s.exceptionsExcluded.size() > 0)) {
throw new AlreadyHasExclusionsException(
"ThrowableSet.Add(ThrowableSet): attempt to add to [" + this.toString() + "] after removals recorded.");
}
// Remove the exceptions
return this.remove(s.exceptionsIncluded);
}
/**
* Indicates whether this ThrowableSet includes some exception that might be caught by a handler argument of the type
* catcher
.
*
* @param catcher
* type of the handler parameter to be tested.
*
* @return true
if this set contains an exception type that might be caught by catcher
, false if
* it does not.
*/
public boolean catchableAs(RefType catcher) {
if (INSTRUMENTING) {
Manager.v().catchableAsQueries++;
}
FastHierarchy h = Scene.v().getOrMakeFastHierarchy();
/**
* Originally this implementation had checked if the catcher.getSootClass() is a phantom class. However this makes
* problems in case the soot option no_bodies_for_excluded==true because certain library classes will be marked as
* phantom classes even if they have a hierarchy. The workaround for this problem is to check for the suerClass. As every
* class except java.lang.Object have a superClass (even interfaces have!) only real phantom classes can be identified
* using this method.
*/
boolean catcherHasNoHierarchy = hasNoHierarchy(catcher);
if (exceptionsExcluded.size() > 0) {
if (INSTRUMENTING) {
Manager.v().catchableAsFromSearch++;
}
for (AnySubType exclusion : exceptionsExcluded) {
if (catcherHasNoHierarchy) {
if (exclusion.getBase().equals(catcher)) {
return false;
}
} else 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 (RefLikeType thrownType : exceptionsIncluded) {
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 (!catcherHasNoHierarchy && h.canStoreType(thrownType, catcher)) {
return true;
}
} else {
RefType thrownBase = ((AnySubType) thrownType).getBase();
if (catcherHasNoHierarchy) {
if (thrownBase.equals(catcher) || thrownBase.getClassName().equals("java.lang.Throwable")) {
return true;
}
}
// At runtime, thrownType might be instantiated by any
// of thrownBase's subtypes, so:
else if (h.canStoreType(thrownBase, catcher) || h.canStoreType(catcher, thrownBase)) {
return true;
}
}
}
return false;
}
}
/**
* Partitions the exceptions in this ThrowableSet
into those which would be caught by a handler with the
* passed catch
parameter type and those which would not.
*
* @param catcher
* type of the handler parameter to be tested.
*
* @return a pair of ThrowableSet
s, one containing the types in this ThrowableSet
which would be
* be caught as catcher
and the other containing the types in this ThrowableSet
which
* would not be caught as catcher
.
*/
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++;
}
boolean catcherHasNoHierarchy = hasNoHierarchy(catcher);
for (AnySubType exclusion : exceptionsExcluded) {
RefType exclusionBase = exclusion.getBase();
// Is the current type explicitly excluded?
if (catcherHasNoHierarchy && exclusionBase.equals(catcher)) {
return new Pair(ThrowableSet.Manager.v().EMPTY, this);
}
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 (RefLikeType inclusion : exceptionsIncluded) {
if (inclusion instanceof RefType) {
// If the current type is has no hierarchy, we catch it if and
// only if it is in the inclusion list and ignore any hierarchy.
if (catcherHasNoHierarchy) {
if (inclusion.equals(catcher)) {
caughtIncluded = addExceptionToSet(inclusion, caughtIncluded);
} else {
uncaughtIncluded = addExceptionToSet(inclusion, uncaughtIncluded);
}
} else if (h.canStoreType(inclusion, catcher)) {
caughtIncluded = addExceptionToSet(inclusion, caughtIncluded);
} else {
uncaughtIncluded = addExceptionToSet(inclusion, uncaughtIncluded);
}
} else {
RefType base = ((AnySubType) inclusion).getBase();
// If the current type is has no hierarchy, we catch it if and
// only if it is in the inclusion list and ignore any hierarchy.
if (catcherHasNoHierarchy) {
if (base.equals(catcher)) {
caughtIncluded = addExceptionToSet(inclusion, caughtIncluded);
} else {
if (base.getClassName().equals("java.lang.Throwable")) {
caughtIncluded = addExceptionToSet(catcher, caughtIncluded);
}
uncaughtIncluded = addExceptionToSet(inclusion, uncaughtIncluded);
}
} else 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);
}
/**
* 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 Set
to which to add the types, or null
if no Set
has yet been
* allocated.
*
* @return A Set
containing the elements in set
plus e
.
*/
private Set addExceptionToSet(T e, Set set) {
if (set == null) {
set = new HashSet<>();
}
set.add(e);
return set;
}
/**
* Returns a string representation of this ThrowableSet
.
*/
@Override
public String toString() {
StringBuffer buffer = new StringBuffer(this.toBriefString());
buffer.append(":\n ");
for (RefLikeType ei : exceptionsIncluded) {
buffer.append('+');
buffer.append(ei == null ? "null" : ei.toString());
// buffer.append(i.next().toString());
}
for (RefLikeType ee : exceptionsExcluded) {
buffer.append('-');
buffer.append(ee.toString());
}
return buffer.toString();
}
/**
* Returns a cryptic identifier for this ThrowableSet
, used to identify a set when it appears in a collection.
*/
public String toBriefString() {
return super.toString();
}
/**
*
* Produce an abbreviated representation of this ThrowableSet
, suitable for human consumption. The
* abbreviations include:
*
*
*
*
* The strings “java.lang.
” is stripped from the beginning of exception names.
*
* The string “Exception
” is stripped from the ends of exception names.
*
* Instances of AnySubType
are indicated by surrounding the base type name with parentheses, rather than
* with the string “ Any_subtype_of_
”
*
* If this ThrowableSet
includes all the elements of {@link ThrowableSet.Manager#VM_ERRORS VM_ERRORS},
* they are abbreviated as “vmErrors
” rather than listed individually.
*
* @return An abbreviated representation of the contents of this set.
*/
public String toAbbreviatedString() {
return toAbbreviatedString(exceptionsIncluded, '+') + toAbbreviatedString(exceptionsExcluded, '-');
}
/**
*
* Utility method which prints the abbreviations of the elements in a passed {@link Set} of exception types.
*
*
* @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 String EXCEPTION = "Exception";
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 = it.next();
RefType baseType = null;
if (reflikeType instanceof RefType) {
baseType = (RefType) reflikeType;
if (containsAllVmErrors && vmErrorThrowables.contains(baseType)) {
continue; // Already accounted for vmErrors.
} else {
buf.append(connector);
}
} else if (reflikeType instanceof AnySubType) {
buf.append(connector);
buf.append('(');
baseType = ((AnySubType) reflikeType).getBase();
} else {
throw new RuntimeException("Unsupported type " + reflikeType.getClass().getName());
}
String typeName = baseType.toString();
int start = 0;
int end = typeName.length();
if (typeName.startsWith(JAVA_LANG)) {
start += JAVA_LANG.length();
}
if (typeName.endsWith(EXCEPTION)) {
end -= EXCEPTION.length();
}
buf.append(typeName, start, end);
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
* Throwable
types included in this set.
*
* @return an unmodifiable collection view of the Throwable
types in this set.
*/
Collection typesIncluded() {
return exceptionsIncluded;
}
/**
* A package-private method to provide unit tests with access to the {@link RefLikeType} objects which represent the
* Throwable
types excluded from this set.
*
* @return an unmodifiable collection view of the Throwable
types excluded from this set.
*/
Collection typesExcluded() {
return exceptionsExcluded;
}
/**
* A package-private method to provide unit tests with access to ThrowableSet's internals.
*/
Map getMemoizedAdds() {
if (memoizedAdds == null) {
return Collections.emptyMap();
} else {
return Collections.unmodifiableMap(memoizedAdds);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + exceptionsIncluded.hashCode();
result = (prime * result) + exceptionsExcluded.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ThrowableSet other = (ThrowableSet) obj;
return exceptionsIncluded.equals(other.exceptionsIncluded) && exceptionsExcluded.equals(other.exceptionsExcluded);
}
/**
* 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 {
/**
* ThrowableSet
containing no exception classes.
*/
public final ThrowableSet EMPTY;
/**
* ThrowableSet
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.
*/
public final ThrowableSet RESOLVE_CLASS_ERRORS;
public final RefType RUNTIME_EXCEPTION;
public final RefType ARITHMETIC_EXCEPTION;
public final RefType ARRAY_STORE_EXCEPTION;
public final RefType CLASS_CAST_EXCEPTION;
public final RefType ILLEGAL_MONITOR_STATE_EXCEPTION;
public final RefType INDEX_OUT_OF_BOUNDS_EXCEPTION;
public final RefType ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION;
public final RefType NEGATIVE_ARRAY_SIZE_EXCEPTION;
public final RefType NULL_POINTER_EXCEPTION;
public final RefType INSTANTIATION_ERROR;
/**
* ThrowableSet
representing all possible Throwables.
*/
final ThrowableSet ALL_THROWABLES;
/**
* ThrowableSet
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;
/**
* ThrowableSet
containing all the exceptions that may be thrown in the course of resolving a reference to a
* field.
*/
final ThrowableSet RESOLVE_FIELD_ERRORS;
/**
* ThrowableSet
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;
/**
* ThrowableSet
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;
/**
* This map stores all referenced ThrowableSet
s.
*/
private final Map registry
= CacheBuilder.newBuilder().weakValues().build().asMap();
private final int removesFromMap = 0;
private final int removesFromMemo = 0;
// counts for instrumenting:
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 int removesFromSearch = 0;
private int registrationCalls = 0;
private int catchableAsQueries = 0;
private int catchableAsFromMap = 0;
private int catchableAsFromSearch = 0;
/**
* Constructs a ThrowableSet.Manager
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 ThrowableSet.Manager
.
*
* @return Soot's ThrowableSet.Manager
.
*/
public static Manager v() {
return G.v().soot_toolkits_exceptions_ThrowableSet_Manager();
}
/**
*
* Returns a ThrowableSet
representing the set of exceptions included in include
minus the set
* of exceptions included in exclude
. Creates a new ThrowableSet
only if there was not already
* one whose contents correspond to include
- exclude
.
*
*
* @param include
* A set of {@link RefLikeType} objects representing exception types included in the result; may be
* null
if there are no included types.
*
* @param exclude
* A set of {@link AnySubType} objects representing exception types excluded from the result; may be
* null
if there are no excluded types.
*
* @return a ThrowableSet
representing the set of exceptions corresponding to include
-
* exclude
.
*/
private ThrowableSet registerSetIfNew(Set include, Set exclude) {
if (INSTRUMENTING) {
registrationCalls++;
}
ThrowableSet result = new ThrowableSet(include, exclude);
ThrowableSet ref = registry.get(result);
if (null != ref) {
return ref;
}
registry.put(result, 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 = registry.size();
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.
*/
Set getThrowableSets() {
return registry.keySet();
}
}
public static class AlreadyHasExclusionsException extends IllegalStateException {
private static final long serialVersionUID = 6785184160868722359L;
public AlreadyHasExclusionsException(String s) {
super(s);
}
}
/**
* 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 ThrowableSet.Pair
.
*
* @param caught
* The set of exceptions to be returned when {@link #getCaught()} is called on the constructed
* ThrowableSet.Pair
.
*
* @param uncaught
* The set of exceptions to be returned when {@link #getUncaught()} is called on the constructed
* ThrowableSet.Pair
.
*/
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 ThrowableSet.Pair
s representing the same set of caught and
* uncaught exception types.
*
* @param o
* the Object
to compare to this ThrowableSet.Pair
.
*
* @return true
if o
is a ThrowableSet.Pair
representing the same set of caught
* and uncaught types as this ThrowableSet.Pair
.
*/
@Override
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;
}
@Override
public int hashCode() {
int result = 31;
result = (37 * result) + caught.hashCode();
result = (37 * result) + uncaught.hashCode();
return result;
}
}
/**
* Comparator used to implement sortedThrowableIterator().
*
*/
private static class ThrowableComparator implements java.util.Comparator {
private static RefType baseType(RefLikeType o) {
if (o instanceof AnySubType) {
return ((AnySubType) o).getBase();
} else {
return (RefType) o; // ClassCastException if o is not a RefType.
}
}
@Override
public int compare(T o1, T 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());
}
}
}
}