/*
* Bytecode Analysis Framework
* Copyright (C) 2003-2006 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;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;
import java.util.Collection;
import javax.annotation.CheckForNull;
import net.jcip.annotations.NotThreadSafe;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import edu.umd.cs.findbugs.AbstractBugReporter;
import edu.umd.cs.findbugs.AnalysisLocal;
import edu.umd.cs.findbugs.Project;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.SuppressionMatcher;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabase;
import edu.umd.cs.findbugs.ba.interproc.PropertyDatabaseFormatException;
import edu.umd.cs.findbugs.ba.jsr305.DirectlyRelevantTypeQualifiersDatabase;
import edu.umd.cs.findbugs.ba.npe.ParameterNullnessPropertyDatabase;
import edu.umd.cs.findbugs.ba.npe.ReturnValueNullnessPropertyDatabase;
import edu.umd.cs.findbugs.ba.type.FieldStoreTypeDatabase;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.FieldOrMethodDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.analysis.ClassData;
import edu.umd.cs.findbugs.classfile.analysis.MethodInfo;
import edu.umd.cs.findbugs.detect.UnreadFields;
import edu.umd.cs.findbugs.detect.UnreadFieldsData;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
/**
* A context for analysis of a complete project. This serves as the repository
* for whole-program information and data structures.
*
* <p>
* <b>NOTE</b>: this class is slated to become obsolete. New code should use the
* IAnalysisCache object returned by Global.getAnalysisCache() to access all
* analysis information (global databases, class and method analyses, etc.)
* </p>
*
* @author David Hovemeyer
* @see edu.umd.cs.findbugs.classfile.IAnalysisCache
* @see edu.umd.cs.findbugs.classfile.Global
*/
@NotThreadSafe
public abstract class AnalysisContext {
public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.analysiscontext.debug");
public static final boolean IGNORE_BUILTIN_MODELS = SystemProperties.getBoolean("findbugs.ignoreBuiltinModels");
public static final String DEFAULT_NONNULL_PARAM_DATABASE_FILENAME = "nonnullParam.db";
public static final String DEFAULT_CHECK_FOR_NULL_PARAM_DATABASE_FILENAME = "checkForNullParam.db";
public static final String DEFAULT_NULL_RETURN_VALUE_ANNOTATION_DATABASE = "nullReturn.db";
public static final String UNCONDITIONAL_DEREF_DB_FILENAME = "unconditionalDeref.db";
public static final String NONNULL_RETURN_DB_FILENAME = "nonnullReturn.db";
public static final String UNCONDITIONAL_DEREF_DB_RESOURCE = "jdkBaseUnconditionalDeref.db";
public static final String NONNULL_RETURN_DB_RESOURCE = "jdkBaseNonnullReturn.db";
public static final String DEFAULT_NULL_RETURN_VALUE_DB_FILENAME = "mayReturnNull.db";
private static InheritableThreadLocal<AnalysisContext> currentAnalysisContext = new InheritableThreadLocal<AnalysisContext>() {
@Override
public AnalysisContext initialValue() {
// throw new
// IllegalStateException("currentAnalysisContext should be set by AnalysisContext.setCurrentAnalysisContext");
return null;
}
};
private static AnalysisLocal<XFactory> currentXFactory = new AnalysisLocal<XFactory>() {
@Override
public XFactory initialValue() {
throw new IllegalStateException("currentXFactory should be set by AnalysisContext.setCurrentAnalysisContext");
}
};
public abstract INullnessAnnotationDatabase getNullnessAnnotationDatabase();
public abstract CheckReturnAnnotationDatabase getCheckReturnAnnotationDatabase();
public abstract AnnotationRetentionDatabase getAnnotationRetentionDatabase();
public abstract JCIPAnnotationDatabase getJCIPAnnotationDatabase();
/**
* save the original SyntheticRepository so we may obtain JavaClass objects
* which we can reuse. (A URLClassPathRepository gets closed after
* analysis.)
*/
private static final org.apache.bcel.util.Repository originalRepository = Repository.getRepository(); // BCEL
// SyntheticRepository
/**
* Default maximum number of ClassContext objects to cache. FIXME: need to
* evaluate this parameter. Need to keep stats about accesses.
*/
private static final int DEFAULT_CACHE_SIZE = 3;
// Instance fields
private BitSet boolPropertySet;
private String databaseInputDir;
private String databaseOutputDir;
protected AnalysisContext() {
this.boolPropertySet = new BitSet();
}
private void clear() {
boolPropertySet = null;
databaseInputDir = null;
databaseOutputDir = null;
}
// /**
// * Create a new AnalysisContext.
// *
// * @param lookupFailureCallback the RepositoryLookupFailureCallback that
// * the AnalysisContext should use to report errors
// * @return a new AnalysisContext
// */
// public static AnalysisContext create(RepositoryLookupFailureCallback
// lookupFailureCallback) {
// AnalysisContext analysisContext = new
// LegacyAnalysisContext(lookupFailureCallback);
// setCurrentAnalysisContext(analysisContext);
// return analysisContext;
// }
/**
* Instantiate the CheckReturnAnnotationDatabase. Do this after the
* repository has been set up.
*/
public abstract void initDatabases();
/**
* After a pass has been completed, allow the analysis context to update
* information.
*
* @param pass
* -- the first pass is pass 0
*/
public abstract void updateDatabases(int pass);
/**
* Get the AnalysisContext associated with this thread
*/
static public AnalysisContext currentAnalysisContext() {
return currentAnalysisContext.get();
}
static public XFactory currentXFactory() {
return currentXFactory.get();
}
ClassSummary classSummary;
public ClassSummary getClassSummary() {
if (classSummary == null)
throw new IllegalStateException("ClassSummary not set");
return classSummary;
}
public void setClassSummary(@NonNull ClassSummary classSummary) {
if (this.classSummary != null)
throw new IllegalStateException("ClassSummary already set");
this.classSummary = classSummary;
}
final EqualsKindSummary equalsKindSummary = new EqualsKindSummary();
public EqualsKindSummary getEqualsKindSummary() {
return equalsKindSummary;
}
FieldSummary fieldSummary;
public FieldSummary getFieldSummary() {
if (fieldSummary == null) {
AnalysisContext.logError("Field Summary not set", new IllegalStateException());
fieldSummary = new FieldSummary();
}
return fieldSummary;
}
public void setFieldSummary(@NonNull FieldSummary fieldSummary) {
if (this.fieldSummary != null) {
AnalysisContext.logError("Field Summary already set", new IllegalStateException());
this.fieldSummary = fieldSummary;
}
this.fieldSummary = fieldSummary;
}
final UnreadFieldsData unreadFieldsData = new UnreadFieldsData();
UnreadFields unreadFields;
public UnreadFieldsData getUnreadFieldsData() {
return unreadFieldsData;
}
public UnreadFields getUnreadFields() {
return unreadFields;
}
public boolean unreadFieldsAvailable() {
return unreadFields != null;
}
public void setUnreadFields(@NonNull UnreadFields unreadFields) {
if (this.unreadFields != null)
throw new IllegalStateException("UnreadFields detector already set");
this.unreadFields = unreadFields;
}
public abstract DirectlyRelevantTypeQualifiersDatabase getDirectlyRelevantTypeQualifiersDatabase();
private static boolean skipReportingMissingClass(@CheckForNull @DottedClassName String missing) {
return missing == null || missing.length() == 0 || missing.charAt(0) == '[' || missing.endsWith("package-info");
}
private static @CheckForNull
RepositoryLookupFailureCallback getCurrentLookupFailureCallback() {
AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
if (currentAnalysisContext2 == null)
return null;
if (currentAnalysisContext2.missingClassWarningsSuppressed)
return null;
return currentAnalysisContext2.getLookupFailureCallback();
}
/**
* file a ClassNotFoundException with the lookupFailureCallback
*
* @see #getLookupFailureCallback()
*/
static public void reportMissingClass(ClassNotFoundException e) {
if (e == null)
throw new NullPointerException("argument is null");
String missing = AbstractBugReporter.getMissingClassName(e);
if (skipReportingMissingClass(missing))
return;
RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
if (lookupFailureCallback != null)
lookupFailureCallback.reportMissingClass(e);
}
static public void reportMissingClass(edu.umd.cs.findbugs.ba.MissingClassException e) {
if (e == null)
throw new NullPointerException("argument is null");
reportMissingClass(e.getClassDescriptor());
}
static public void reportMissingClass(edu.umd.cs.findbugs.classfile.MissingClassException e) {
if (e == null)
throw new NullPointerException("argument is null");
reportMissingClass(e.getClassDescriptor());
}
static public void reportMissingClass(ClassDescriptor c) {
if (c == null)
throw new NullPointerException("argument is null");
String missing = c.getDottedClassName();
if (missing.length() == 1)
System.out.println(c);
if (skipReportingMissingClass(missing))
return;
RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback();
if (lookupFailureCallback != null)
lookupFailureCallback.reportMissingClass(c);
}
/**
* Report an error
*/
static public void logError(String msg, Exception e) {
if (e instanceof MissingClassException) {
reportMissingClass(((MissingClassException) e).getClassNotFoundException());
return;
}
if (e instanceof edu.umd.cs.findbugs.classfile.MissingClassException) {
reportMissingClass(((edu.umd.cs.findbugs.classfile.MissingClassException) e).toClassNotFoundException());
return;
}
AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
if (currentAnalysisContext2 == null)
return;
RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2.getLookupFailureCallback();
if (lookupFailureCallback != null)
lookupFailureCallback.logError(msg, e);
}
/**
* Report an error
*/
static public void logError(String msg) {
AnalysisContext currentAnalysisContext2 = currentAnalysisContext();
if (currentAnalysisContext2 == null)
return;
RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2.getLookupFailureCallback();
if (lookupFailureCallback != null)
lookupFailureCallback.logError(msg);
}
boolean missingClassWarningsSuppressed = false;
protected Project project;
public boolean setMissingClassWarningsSuppressed(boolean value) {
boolean oldValue = missingClassWarningsSuppressed;
missingClassWarningsSuppressed = value;
return oldValue;
}
/**
* Get the lookup failure callback.
*/
public abstract RepositoryLookupFailureCallback getLookupFailureCallback();
/**
* Set the source path.
*/
public final void setProject(Project project) {
this.project = project;
}
/**
* Get the SourceFinder, for finding source files.
*/
public abstract SourceFinder getSourceFinder();
// /**
// * Get the Subtypes database.
// *
// * @return the Subtypes database
// */
// @Deprecated // use Subtypes2 instead
// public abstract Subtypes getSubtypes();
/**
* Clear the BCEL Repository in preparation for analysis.
*/
public abstract void clearRepository();
/**
* Clear the ClassContext cache. This should be done between analysis
* passes.
*/
public abstract void clearClassContextCache();
/**
* Add an entry to the Repository's classpath.
*
* @param url
* the classpath entry URL
* @throws IOException
*/
public abstract void addClasspathEntry(String url) throws IOException;
// /**
// * Add an application class to the repository.
// *
// * @param appClass the application class
// */
// public abstract void addApplicationClassToRepository(JavaClass appClass);
/**
* Return whether or not the given class is an application class.
*
* @param cls
* the class to lookup
* @return true if the class is an application class, false if not an
* application class or if the class cannot be located
*/
public boolean isApplicationClass(JavaClass cls) {
// return getSubtypes().isApplicationClass(cls);
return getSubtypes2().isApplicationClass(DescriptorFactory.createClassDescriptor(cls));
}
/**
* Return whether or not the given class is an application class.
*
* @param className
* name of a class
* @return true if the class is an application class, false if not an
* application class or if the class cannot be located
*/
public boolean isApplicationClass(@DottedClassName String className) {
// try {
// JavaClass javaClass = lookupClass(className);
// return isApplicationClass(javaClass);
// } catch (ClassNotFoundException e) {
// AnalysisContext.reportMissingClass(e);
// return false;
// }
ClassDescriptor classDesc = DescriptorFactory.createClassDescriptorFromDottedClassName(className);
return getSubtypes2().isApplicationClass(classDesc);
}
public boolean isApplicationClass(ClassDescriptor desc) {
return getSubtypes2().isApplicationClass(desc);
}
public boolean isTooBig(ClassDescriptor desc) {
IAnalysisCache analysisCache = Global.getAnalysisCache();
try {
ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, desc);
ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc);
if (classData.getData().length > 1000000)
return true;
try {
JavaClass javaClass = classContext.getJavaClass();
if (javaClass.getMethods().length > 1000)
return true;
} catch (RuntimeException e) {
AnalysisContext.logError("Error parsing class " + desc
+ " from " + classData.getCodeBaseEntry().getCodeBase(), e);
return true;
}
} catch (RuntimeException e) {
AnalysisContext.logError("Error getting class data for " + desc, e);
return true;
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Could not get class context for " + desc, e);
return true;
}
return false;
}
/**
* Lookup a class.
* <em>Use this method instead of Repository.lookupClass().</em>
*
* @param className
* the name of the class
* @return the JavaClass representing the class
* @throws ClassNotFoundException
* (but not really)
*/
public abstract JavaClass lookupClass(@NonNull @DottedClassName String className) throws ClassNotFoundException;
/**
* Lookup a class.
* <em>Use this method instead of Repository.lookupClass().</em>
*
* @param classDescriptor
* descriptor specifying the class to look up
* @return the class
* @throws ClassNotFoundException
* if the class can't be found
*/
public JavaClass lookupClass(@NonNull ClassDescriptor classDescriptor) throws ClassNotFoundException {
return lookupClass(classDescriptor.toDottedClassName());
}
/**
* This is equivalent to Repository.lookupClass() or this.lookupClass(),
* except it uses the original Repository instead of the current one.
*
* This can be important because URLClassPathRepository objects are closed
* after an analysis, so JavaClass objects obtained from them are no good on
* subsequent runs.
*
* @param className
* the name of the class
* @return the JavaClass representing the class
* @throws ClassNotFoundException
*/
public static JavaClass lookupSystemClass(@NonNull String className) throws ClassNotFoundException {
// TODO: eventually we should move to our own thread-safe repository
// implementation
if (className == null)
throw new IllegalArgumentException("className is null");
if (originalRepository == null)
throw new IllegalStateException("originalRepository is null");
JavaClass clazz = originalRepository.findClass(className);
return (clazz == null ? originalRepository.loadClass(className) : clazz);
}
/**
* Lookup a class's source file
*
* @param dottedClassName
* the name of the class
* @return the source file for the class, or
* {@link SourceLineAnnotation#UNKNOWN_SOURCE_FILE} if unable to
* determine
*/
public final String lookupSourceFile(@NonNull @DottedClassName String dottedClassName) {
if (dottedClassName == null)
throw new IllegalArgumentException("className is null");
try {
XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class,
DescriptorFactory.createClassDescriptorFromDottedClassName(dottedClassName));
String name = xClass.getSource();
if (name == null) {
return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
}
return name;
} catch (CheckedAnalysisException e) {
return SourceLineAnnotation.UNKNOWN_SOURCE_FILE;
}
}
/**
* Get the ClassContext for a class.
*
* @param javaClass
* the class
* @return the ClassContext for that class
*/
public abstract ClassContext getClassContext(JavaClass javaClass);
/**
* Get stats about hit rate for ClassContext cache.
*
* @return stats about hit rate for ClassContext cache
*/
public abstract String getClassContextStats();
/**
* If possible, load interprocedural property databases.
*/
public final void loadInterproceduralDatabases() {
loadPropertyDatabase(getFieldStoreTypeDatabase(), FieldStoreTypeDatabase.DEFAULT_FILENAME, "field store type database");
loadPropertyDatabase(getUnconditionalDerefParamDatabase(), UNCONDITIONAL_DEREF_DB_FILENAME,
"unconditional param deref database");
loadPropertyDatabase(getReturnValueNullnessPropertyDatabase(), NONNULL_RETURN_DB_FILENAME, "nonnull return db database");
}
/**
* If possible, load default (built-in) interprocedural property databases.
* These are the databases for things like Java core APIs that unconditional
* dereference parameters.
*/
public final void loadDefaultInterproceduralDatabases() {
if (IGNORE_BUILTIN_MODELS)
return;
loadPropertyDatabaseFromResource(getUnconditionalDerefParamDatabase(), UNCONDITIONAL_DEREF_DB_RESOURCE,
"unconditional param deref database");
loadPropertyDatabaseFromResource(getReturnValueNullnessPropertyDatabase(), NONNULL_RETURN_DB_RESOURCE,
"nonnull return db database");
}
/**
* Set a boolean property.
*
* @param prop
* the property to set
* @param value
* the value of the property
*/
public final void setBoolProperty(int prop, boolean value) {
boolPropertySet.set(prop, value);
}
/**
* Get a boolean property.
*
* @param prop
* the property
* @return value of the property; defaults to false if the property has not
* had a value assigned explicitly
*/
public final boolean getBoolProperty(int prop) {
return boolPropertySet.get(prop);
}
/**
* Get the SourceInfoMap.
*/
public abstract SourceInfoMap getSourceInfoMap();
/**
* Set the interprocedural database input directory.
*
* @param databaseInputDir
* the interprocedural database input directory
*/
public final void setDatabaseInputDir(String databaseInputDir) {
if (DEBUG)
System.out.println("Setting database input directory: " + databaseInputDir);
this.databaseInputDir = databaseInputDir;
}
/**
* Get the interprocedural database input directory.
*
* @return the interprocedural database input directory
*/
public final String getDatabaseInputDir() {
return databaseInputDir;
}
/**
* Set the interprocedural database output directory.
*
* @param databaseOutputDir
* the interprocedural database output directory
*/
public final void setDatabaseOutputDir(String databaseOutputDir) {
if (DEBUG)
System.out.println("Setting database output directory: " + databaseOutputDir);
this.databaseOutputDir = databaseOutputDir;
}
/**
* Get the interprocedural database output directory.
*
* @return the interprocedural database output directory
*/
public final String getDatabaseOutputDir() {
return databaseOutputDir;
}
/**
* Get the property database recording the types of values stored into
* fields.
*
* @return the database, or null if there is no database available
*/
public abstract FieldStoreTypeDatabase getFieldStoreTypeDatabase();
/**
* Get the property database recording which methods unconditionally
* dereference parameters.
*
* @return the database, or null if there is no database available
*/
public abstract ParameterNullnessPropertyDatabase getUnconditionalDerefParamDatabase();
/**
* Get the property database recording which methods always return nonnull
* values
*
* @return the database, or null if there is no database available
*/
public abstract ReturnValueNullnessPropertyDatabase getReturnValueNullnessPropertyDatabase();
/**
* Load an interprocedural property database.
*
* @param <DatabaseType>
* actual type of the database
* @param <KeyType>
* type of key (e.g., method or field)
* @param <Property>
* type of properties stored in the database
* @param database
* the empty database object
* @param fileName
* file to load database from
* @param description
* description of the database (for diagnostics)
* @return the database object, or null if the database couldn't be loaded
*/
public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabase(
DatabaseType database, String fileName, String description) {
try {
File dbFile = new File(getDatabaseInputDir(), fileName);
if (DEBUG)
System.out.println("Loading " + description + " from " + dbFile.getPath() + "...");
database.readFromFile(dbFile.getPath());
return database;
} catch (IOException e) {
getLookupFailureCallback().logError("Error loading " + description, e);
} catch (PropertyDatabaseFormatException e) {
getLookupFailureCallback().logError("Invalid " + description, e);
}
return null;
}
/**
* Load an interprocedural property database.
*
* @param <DatabaseType>
* actual type of the database
* @param <KeyType>
* type of key (e.g., method or field)
* @param <Property>
* type of properties stored in the database
* @param database
* the empty database object
* @param resourceName
* name of resource to load the database from
* @param description
* description of the database (for diagnostics)
* @return the database object, or null if the database couldn't be loaded
*/
public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> DatabaseType loadPropertyDatabaseFromResource(
DatabaseType database, String resourceName, String description) {
try {
if (DEBUG)
System.out.println("Loading default " + description + " from " + resourceName + " @ "
+ database.getClass().getResource(resourceName) + " ... ");
InputStream in = database.getClass().getResourceAsStream(resourceName);
database.read(in);
in.close();
return database;
} catch (IOException e) {
getLookupFailureCallback().logError("Error loading " + description, e);
} catch (PropertyDatabaseFormatException e) {
getLookupFailureCallback().logError("Invalid " + description, e);
}
return null;
}
/**
* Write an interprocedural property database.
*
* @param <DatabaseType>
* actual type of the database
* @param <KeyType>
* type of key (e.g., method or field)
* @param <Property>
* type of properties stored in the database
* @param database
* the database
* @param fileName
* name of database file
* @param description
* description of the database
*/
public <DatabaseType extends PropertyDatabase<KeyType, Property>, KeyType extends FieldOrMethodDescriptor, Property> void storePropertyDatabase(
DatabaseType database, String fileName, String description) {
try {
File dbFile = new File(getDatabaseOutputDir(), fileName);
if (DEBUG)
System.out.println("Writing " + description + " to " + dbFile.getPath() + "...");
database.writeToFile(dbFile.getPath());
} catch (IOException e) {
getLookupFailureCallback().logError("Error writing " + description, e);
}
}
public abstract InnerClassAccessMap getInnerClassAccessMap();
/**
* Set the current analysis context for this thread.
*
* @param analysisContext
* the current analysis context for this thread
*/
public static void setCurrentAnalysisContext(AnalysisContext analysisContext) {
currentAnalysisContext.set(analysisContext);
currentXFactory.set(new XFactory());
}
public static void removeCurrentAnalysisContext() {
AnalysisContext context = currentAnalysisContext();
if (context != null)
context.clear();
currentAnalysisContext.remove();
}
/**
* Get the Subtypes2 inheritance hierarchy database.
*/
public abstract Subtypes2 getSubtypes2();
/**
* Get Collection of all XClass objects seen so far.
*
* @return Collection of all XClass objects seen so far
*/
public Collection<XClass> getXClassCollection() {
return getSubtypes2().getXClassCollection();
}
public abstract @CheckForNull
XMethod getBridgeTo(MethodInfo m);
public abstract @CheckForNull
XMethod getBridgeFrom(MethodInfo m);
public abstract void setBridgeMethod(MethodInfo from, MethodInfo to);
private final SuppressionMatcher suppressionMatcher = new SuppressionMatcher();
public SuppressionMatcher getSuppressionMatcher() {
return suppressionMatcher;
}
}
// vim:ts=4