/** Set the list of source directories. */ void setSourceBaseList(Iterable<String> sourceBaseList) { for (String repos : sourceBaseList) { if (repos.endsWith(".zip") || repos.endsWith(".jar") || repos.endsWith(".z0p.gz")) { // Zip or jar archive try { if (repos.startsWith("http:") || repos.startsWith("https:") || repos.startsWith("file:")) { String url = SystemProperties.rewriteURLAccordingToProperties(repos); repositoryList.add(makeInMemorySourceRepository(url)); } else repositoryList.add(new ZipSourceRepository(new ZipFile(repos))); } catch (IOException e) { // Ignored - we won't use this archive AnalysisContext.logError("Unable to load " + repos, e); } } else { File dir = new File(repos); if (dir.canRead() && dir.isDirectory()) repositoryList.add(new DirectorySourceRepository(repos)); else { AnalysisContext.logError("Unable to load " + repos); } } } }
/** * 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.) * * @author David Hovemeyer * @see edu.umd.cs.findbugs.classfile.IAnalysisCache * @see edu.umd.cs.findbugs.classfile.Global */ @NotThreadSafe public 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"); } }; /** * 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; boolean missingClassWarningsSuppressed; private ClassSummary classSummary; private ClassDescriptor classBeingAnalyzed; private FieldSummary fieldSummary; private UnreadFields unreadFields; private TypeQualifierNullnessAnnotationDatabase tqNullnessDatabase; private final HashSet<MethodDescriptor> skippedDueToInvokeDynamic; private final Project project; private final EqualsKindSummary equalsKindSummary; private final UnreadFieldsData unreadFieldsData; private final SuppressionMatcher suppressionMatcher; protected final RepositoryLookupFailureCallback lookupFailureCallback; private final Map<MethodInfo, MethodInfo> bridgeTo; private final Map<MethodInfo, MethodInfo> bridgeFrom; public AnalysisContext(@Nonnull Project project) { requireNonNull(project); this.project = project; this.boolPropertySet = new BitSet(); this.lookupFailureCallback = new DelegatingRepositoryLookupFailureCallback(); skippedDueToInvokeDynamic = new HashSet<>(); equalsKindSummary = new EqualsKindSummary(); unreadFieldsData = new UnreadFieldsData(); suppressionMatcher = new SuppressionMatcher(); bridgeTo = new IdentityHashMap<>(); bridgeFrom = new IdentityHashMap<>(); } private void clear() { boolPropertySet = null; databaseInputDir = null; databaseOutputDir = null; } /** Get the AnalysisContext associated with this thread */ public static AnalysisContext currentAnalysisContext() { return currentAnalysisContext.get(); } public static XFactory currentXFactory() { return currentXFactory.get(); } public ClassDescriptor getClassBeingAnalyzed() { return classBeingAnalyzed; } public void setClassBeingAnalyzed(@Nonnull ClassDescriptor classBeingAnalyzed) { this.classBeingAnalyzed = classBeingAnalyzed; } public void clearClassBeingAnalyzed() { this.classBeingAnalyzed = null; } 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; } public EqualsKindSummary getEqualsKindSummary() { return equalsKindSummary; } 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; } public @Nonnull UnreadFieldsData getUnreadFieldsData() { return unreadFieldsData; } public @Nonnull UnreadFields getUnreadFields() { if (!unreadFieldsAvailable()) { throw new IllegalStateException(); } 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; } 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() */ public static void reportMissingClass(ClassNotFoundException e) { requireNonNull(e, "argument is null"); String missing = AbstractBugReporter.getMissingClassName(e); if (skipReportingMissingClass(missing)) { return; } if (!analyzingApplicationClass()) { return; } RepositoryLookupFailureCallback lookupFailureCallback = getCurrentLookupFailureCallback(); if (lookupFailureCallback != null) { lookupFailureCallback.reportMissingClass(e); } } public static void reportMissingClass(edu.umd.cs.findbugs.ba.MissingClassException e) { requireNonNull(e, "argument is null"); reportMissingClass(e.getClassDescriptor()); } public static boolean analyzingApplicationClass() { AnalysisContext context = AnalysisContext.currentAnalysisContext(); if (context == null) { return false; } ClassDescriptor clazz = context.getClassBeingAnalyzed(); if (clazz == null) { return false; } return context.isApplicationClass(clazz); } public static void reportMissingClass(edu.umd.cs.findbugs.classfile.MissingClassException e) { requireNonNull(e, "argument is null"); reportMissingClass(e.getClassDescriptor()); } public static void reportMissingClass(ClassDescriptor c) { requireNonNull(c, "argument is null"); if (!analyzingApplicationClass()) { return; } 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 */ public static void logError(String msg, Exception e) { AnalysisContext currentAnalysisContext2 = currentAnalysisContext(); if (currentAnalysisContext2 == null) { if (e instanceof NoSuchBugPattern) { return; } /* if (false && SystemProperties.ASSERTIONS_ENABLED) { AssertionError e2 = new AssertionError("Exception logged with no analysis context"); e2.initCause(e); throw e2; } */ e.printStackTrace(System.err); return; } 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; } RepositoryLookupFailureCallback lookupFailureCallback = currentAnalysisContext2.getLookupFailureCallback(); if (lookupFailureCallback != null) { lookupFailureCallback.logError(msg, e); } } /** Report an error */ public static void logError(String msg) { AnalysisContext currentAnalysisContext2 = currentAnalysisContext(); if (currentAnalysisContext2 == null) { return; } currentAnalysisContext2.logAnError(msg); } public void logAnError(String msg) { RepositoryLookupFailureCallback lookupFailureCallback = getLookupFailureCallback(); if (lookupFailureCallback != null) { lookupFailureCallback.logError(msg); } } public void analysisSkippedDueToInvokeDynamic(XMethod m) { if (!m.usesInvokeDynamic()) { throw new IllegalArgumentException(); } if (skippedDueToInvokeDynamic.add(m.getMethodDescriptor())) { logAnError(m + " skipped due to invoke_dynamic"); } } public boolean setMissingClassWarningsSuppressed(boolean value) { boolean oldValue = missingClassWarningsSuppressed; missingClassWarningsSuppressed = value; return oldValue; } // /** // * 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 int getClassSize(ClassDescriptor desc) { IAnalysisCache analysisCache = Global.getAnalysisCache(); try { /* ClassContext classContext =*/ analysisCache.getClassAnalysis(ClassContext.class, desc); ClassData classData = analysisCache.getClassAnalysis(ClassData.class, desc); return classData.getData().length; } catch (RuntimeException e) { AnalysisContext.logError("Error getting class data for " + desc, e); return 10000; } catch (CheckedAnalysisException e) { AnalysisContext.logError("Could not get class context for " + desc, e); return 10000; } } 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 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. * * <p>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 requireNonNull(className, "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) { requireNonNull(dottedClassName, "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; } } /** 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(@AnalysisFeature 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(@AnalysisFeature int prop) { return boolPropertySet.get(prop); } /** * 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; } /** * 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) + " ... "); } try (InputStream in = database.getClass().getResourceAsStream(resourceName)) { if (in == null) { AnalysisContext.logError( "Unable to load " + description + " from resource " + resourceName); } else { database.read(in); } } 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); } } /** * 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); if (Global.getAnalysisCache() != null) { currentXFactory.set(new XFactory()); } } public static void removeCurrentAnalysisContext() { AnalysisContext context = currentAnalysisContext(); if (context != null) { context.clear(); } currentAnalysisContext.remove(); } /** * 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 SuppressionMatcher getSuppressionMatcher() { return suppressionMatcher; } /** * Add an entry to the Repository's classpath. * * @param url the classpath entry URL * @throws IOException */ public void addClasspathEntry(String url) throws IOException { throw new UnsupportedOperationException(); } /** Clear the ClassContext cache. This should be done between analysis passes. */ public void clearClassContextCache() { throw new UnsupportedOperationException(); } /** Clear the BCEL Repository in preparation for analysis. */ public void clearRepository() { // Set the backing store for the BCEL Repository to // be the AnalysisCache. Repository.setRepository(new AnalysisCacheToRepositoryAdapter()); } public AnnotationRetentionDatabase getAnnotationRetentionDatabase() { return getDatabase(AnnotationRetentionDatabase.class); } public CheckReturnAnnotationDatabase getCheckReturnAnnotationDatabase() { return getDatabase(CheckReturnAnnotationDatabase.class); } /** * Get the ClassContext for a class. * * @param javaClass the class * @return the ClassContext for that class */ public ClassContext getClassContext(JavaClass javaClass) { // This is a bit silly since we're doing an unnecessary // ClassDescriptor->JavaClass lookup. // However, we can be assured that it will succeed. ClassDescriptor classDescriptor = DescriptorFactory.instance() .getClassDescriptor(ClassName.toSlashedClassName(javaClass.getClassName())); try { return Global.getAnalysisCache().getClassAnalysis(ClassContext.class, classDescriptor); } catch (CheckedAnalysisException e) { IllegalStateException ise = new IllegalStateException("Could not get ClassContext for JavaClass"); ise.initCause(e); throw ise; } } /** * Get stats about hit rate for ClassContext cache. * * @return stats about hit rate for ClassContext cache */ public String getClassContextStats() { return "<unknown ClassContext stats>"; } /** * Get the property database recording the types of values stored into fields. * * @return the database, or null if there is no database available */ public FieldStoreTypeDatabase getFieldStoreTypeDatabase() { return getDatabase(FieldStoreTypeDatabase.class); } public JCIPAnnotationDatabase getJCIPAnnotationDatabase() { return getDatabase(JCIPAnnotationDatabase.class); } /** Get the lookup failure callback. */ public RepositoryLookupFailureCallback getLookupFailureCallback() { return lookupFailureCallback; } /** Get the SourceFinder, for finding source files. */ public SourceFinder getSourceFinder() { return project.getSourceFinder(); } /** Get the SourceInfoMap. */ public SourceInfoMap getSourceInfoMap() { return getDatabase(SourceInfoMap.class); } /** * Get the property database recording which methods unconditionally dereference parameters. * * @return the database, or null if there is no database available */ public ParameterNullnessPropertyDatabase getUnconditionalDerefParamDatabase() { return getDatabase(ParameterNullnessPropertyDatabase.class); } /** * Instantiate the CheckReturnAnnotationDatabase. Do this after the repository has been set up. */ public void initDatabases() { // Databases are created on-demand - don't need to explicitly create // them } /** * 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 JavaClass lookupClass(@Nonnull @DottedClassName String className) throws ClassNotFoundException { try { if (className.length() == 0) { throw new IllegalArgumentException("Class name is empty"); } if (!ClassName.isValidClassName(className)) { throw new ClassNotFoundException("Invalid class name: " + className); } return Global.getAnalysisCache() .getClassAnalysis( JavaClass.class, DescriptorFactory.instance() .getClassDescriptor(ClassName.toSlashedClassName(className))); } catch (CheckedAnalysisException e) { throw new ClassNotFoundException("Class not found: " + className, e); } } public InnerClassAccessMap getInnerClassAccessMap() { return getDatabase(InnerClassAccessMap.class); } public void setAppClassList(List<ClassDescriptor> appClassCollection) { // FIXME: we really should drive the progress callback here HashSet<ClassDescriptor> appSet = new HashSet<ClassDescriptor>(appClassCollection); Collection<ClassDescriptor> allClassDescriptors = new ArrayList<ClassDescriptor>(DescriptorFactory.instance().getAllClassDescriptors()); for (ClassDescriptor appClass : allClassDescriptors) { try { XClass xclass = currentXFactory().getXClass(appClass); if (xclass == null) { continue; } // Add the application class to the database if (appSet.contains(appClass)) { getSubtypes2().addApplicationClass(xclass); } else if (xclass instanceof ClassInfo) { getSubtypes2().addClass(xclass); } } catch (Exception e) { AnalysisContext.logError("Unable to get XClass for " + appClass, e); } } if (true && Subtypes2.DEBUG) { System.out.println( getSubtypes2().getGraph().getNumVertices() + " vertices in inheritance graph"); } } /** * After a pass has been completed, allow the analysis context to update information. * * @param pass -- the first pass is pass 0 */ public void updateDatabases(int pass) { if (pass == 0) { getCheckReturnAnnotationDatabase().loadAuxiliaryAnnotations(); getNullnessAnnotationDatabase().loadAuxiliaryAnnotations(); } } /** * Get the property database recording which methods always return nonnull values * * @return the database, or null if there is no database available */ public ReturnValueNullnessPropertyDatabase getReturnValueNullnessPropertyDatabase() { return getDatabase(ReturnValueNullnessPropertyDatabase.class); } /** Get the Subtypes2 inheritance hierarchy database. */ public Subtypes2 getSubtypes2() { return Global.getAnalysisCache().getDatabase(Subtypes2.class); } public DirectlyRelevantTypeQualifiersDatabase getDirectlyRelevantTypeQualifiersDatabase() { return Global.getAnalysisCache().getDatabase(DirectlyRelevantTypeQualifiersDatabase.class); } @CheckForNull public XMethod getBridgeTo(MethodInfo m) { return bridgeTo.get(m); } @CheckForNull public XMethod getBridgeFrom(MethodInfo m) { return bridgeFrom.get(m); } public void setBridgeMethod(MethodInfo from, MethodInfo to) { bridgeTo.put(from, to); bridgeFrom.put(to, from); } public TypeQualifierNullnessAnnotationDatabase getNullnessAnnotationDatabase() { if (tqNullnessDatabase == null) { tqNullnessDatabase = new TypeQualifierNullnessAnnotationDatabase(); } return tqNullnessDatabase; } protected <E> E getDatabase(Class<E> cls) { return Global.getAnalysisCache().getDatabase(cls); } static class DelegatingRepositoryLookupFailureCallback implements RepositoryLookupFailureCallback { @Override public void logError(String message) { Global.getAnalysisCache().getErrorLogger().logError(message); } @Override public void logError(String message, Throwable e) { Global.getAnalysisCache().getErrorLogger().logError(message, e); } @Override public void reportMissingClass(ClassNotFoundException ex) { Global.getAnalysisCache().getErrorLogger().reportMissingClass(ex); } @Override public void reportMissingClass(ClassDescriptor classDescriptor) { Global.getAnalysisCache().getErrorLogger().reportMissingClass(classDescriptor); } @Override public void reportSkippedAnalysis(MethodDescriptor method) { Global.getAnalysisCache().getErrorLogger().reportSkippedAnalysis(method); } } }
/** * Implementation of INullnessAnnotationDatabase that is based on JSR-305 type qualifiers. * * @author David Hovemeyer */ public class TypeQualifierNullnessAnnotationDatabase implements INullnessAnnotationDatabase { private static final boolean DEBUG = SystemProperties.getBoolean("findbugs.npe.tq.debug"); public final TypeQualifierValue nonnullTypeQualifierValue; public TypeQualifierNullnessAnnotationDatabase() { ClassDescriptor nonnullClassDesc = DescriptorFactory.createClassDescriptor(javax.annotation.Nonnull.class); this.nonnullTypeQualifierValue = TypeQualifierValue.getValue(nonnullClassDesc, null); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#getResolvedAnnotation(java.lang.Object, boolean) */ public NullnessAnnotation getResolvedAnnotation(Object o, boolean getMinimal) { Profiler profiler = Global.getAnalysisCache().getProfiler(); profiler.start(this.getClass()); try { if (DEBUG) { System.out.println("getResolvedAnnotation: o=" + o + "..."); } TypeQualifierAnnotation tqa = null; if (o instanceof XMethodParameter) { XMethodParameter param = (XMethodParameter) o; tqa = TypeQualifierApplications.getEffectiveTypeQualifierAnnotation( param.getMethod(), param.getParameterNumber(), nonnullTypeQualifierValue); } else if (o instanceof XMethod || o instanceof XField) { tqa = TypeQualifierApplications.getEffectiveTypeQualifierAnnotation( (AnnotatedObject) o, nonnullTypeQualifierValue); } NullnessAnnotation result = toNullnessAnnotation(tqa); if (DEBUG) { System.out.println(" ==> " + (result != null ? result.toString() : "not found")); } return result; } finally { profiler.end(this.getClass()); } } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#parameterMustBeNonNull(edu.umd.cs.findbugs.ba.XMethod, int) */ public boolean parameterMustBeNonNull(XMethod m, int param) { if (DEBUG) { System.out.print("Checking " + m + " param " + param + " for @Nonnull..."); } TypeQualifierAnnotation tqa = TypeQualifierApplications.getEffectiveTypeQualifierAnnotation( m, param, nonnullTypeQualifierValue); if (tqa == null && param == 0) { String name = m.getName(); String signature = m.getSignature(); if (name.equals("main") && signature.equals("([Ljava/lang/String;)V") && m.isStatic() && m.isPublic()) return true; else if (NullnessAnnotationDatabase.assertsFirstParameterIsNonnull(m)) return true; else if (name.equals("compareTo") && signature.substring(signature.indexOf(";") + 1).equals(")Z") && !m.isStatic()) return true; } boolean answer = (tqa != null) && tqa.when == When.ALWAYS; if (DEBUG) { System.out.println(answer ? "yes" : "no"); } return answer; } // NOTE: // The way we handle adding default annotations is to actually add AnnotationValues // to the corresponding XFoo objects, giving the illusion that the annotations // were actually read from the underlying class files. /** * Convert a NullnessAnnotation into the ClassDescriptor of the equivalent JSR-305 nullness type * qualifier. * * @param n a NullnessAnnotation * @return ClassDescriptor of the equivalent JSR-305 nullness type qualifier */ private ClassDescriptor getNullnessAnnotationClassDescriptor(NullnessAnnotation n) { if (n == NullnessAnnotation.CHECK_FOR_NULL) { return JSR305NullnessAnnotations.CHECK_FOR_NULL; } else if (n == NullnessAnnotation.NONNULL) { return JSR305NullnessAnnotations.NONNULL; } else if (n == NullnessAnnotation.NULLABLE) { return JSR305NullnessAnnotations.NULLABLE; } else if (n == NullnessAnnotation.UNKNOWN_NULLNESS) { return JSR305NullnessAnnotations.NULLABLE; } else { throw new IllegalArgumentException("Unknown NullnessAnnotation: " + n); } } private static final ClassDescriptor PARAMETERS_ARE_NONNULL_BY_DEFAULT = DescriptorFactory.createClassDescriptor(javax.annotation.ParametersAreNonnullByDefault.class); private static final ClassDescriptor RETURN_VALUES_ARE_NONNULL_BY_DEFAULT = DescriptorFactory.createClassDescriptor( edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault.class); /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addDefaultAnnotation(java.lang.String, java.lang.String, edu.umd.cs.findbugs.ba.NullnessAnnotation) */ public void addDefaultAnnotation(Target target, String c, NullnessAnnotation n) { if (DEBUG) { System.out.println("addDefaultAnnotation: target=" + target + ", c=" + c + ", n=" + n); } ClassDescriptor classDesc = DescriptorFactory.instance().getClassDescriptorForDottedClassName(c); ClassInfo xclass; // Get the XClass (really a ClassInfo object) try { xclass = (ClassInfo) Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc); } catch (MissingClassException e) { // // AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassDescriptor()); return; } catch (CheckedAnalysisException e) { // AnalysisContext.logError("Error adding built-in nullness annotation", e); return; } if (n == NullnessAnnotation.NONNULL && target == AnnotationDatabase.Target.PARAMETER) { xclass.addAnnotation(new AnnotationValue(PARAMETERS_ARE_NONNULL_BY_DEFAULT)); return; } else if (n == NullnessAnnotation.NONNULL && target == AnnotationDatabase.Target.METHOD) { xclass.addAnnotation(new AnnotationValue(RETURN_VALUES_ARE_NONNULL_BY_DEFAULT)); return; } // Get the default annotation type ClassDescriptor defaultAnnotationType; if (target == AnnotationDatabase.Target.ANY) { defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION; } else if (target == AnnotationDatabase.Target.FIELD) { defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_FIELDS; } else if (target == AnnotationDatabase.Target.METHOD) { defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_METHODS; } else if (target == AnnotationDatabase.Target.PARAMETER) { defaultAnnotationType = FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_PARAMETERS; } else { throw new IllegalArgumentException("Unknown target for default annotation: " + target); } // Get the JSR-305 nullness annotation type ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(n); // Construct an AnnotationValue containing the default annotation AnnotationValue annotationValue = new AnnotationValue(defaultAnnotationType); AnnotationVisitor v = annotationValue.getAnnotationVisitor(); v.visit("value", Type.getObjectType(nullnessAnnotationType.getClassName())); v.visitEnd(); if (DEBUG) { System.out.println("Adding AnnotationValue " + annotationValue + " to class " + xclass); } // Destructively add the annotation to the ClassInfo object xclass.addAnnotation(annotationValue); } // /* (non-Javadoc) // * @see // edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addDefaultMethodAnnotation(java.lang.String, // edu.umd.cs.findbugs.ba.NullnessAnnotation) // */ // public void addDefaultMethodAnnotation(String name, NullnessAnnotation annotation) { // } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addFieldAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, edu.umd.cs.findbugs.ba.NullnessAnnotation) */ public void addFieldAnnotation( String cName, String mName, String mSig, boolean isStatic, NullnessAnnotation annotation) { if (DEBUG) { System.out.println( "addFieldAnnotation: annotate " + cName + "." + mName + " with " + annotation); } XField xfield = XFactory.createXField(cName, mName, mSig, isStatic); if (!(xfield instanceof FieldInfo)) { if (DEBUG) { System.out.println( " Field not found! " + cName + "." + mName + ":" + mSig + " " + isStatic + " " + annotation); } return; } // Get JSR-305 nullness annotation type ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation); // Create an AnnotationValue AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType); // Destructively add the annotation to the FieldInfo object ((FieldInfo) xfield).addAnnotation(annotationValue); } public @CheckForNull XMethod getXMethod( String cName, String mName, String sig, boolean isStatic) { ClassDescriptor classDesc = DescriptorFactory.instance().getClassDescriptorForDottedClassName(cName); ClassInfo xclass; // Get the XClass (really a ClassInfo object) try { xclass = (ClassInfo) Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc); } catch (MissingClassException e) { if (DEBUG) { System.out.println(" Class not found!"); } // // AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassDescriptor()); return null; } catch (CheckedAnalysisException e) { if (DEBUG) { System.out.println(" Class not found!"); } // AnalysisContext.logError("Error adding built-in nullness annotation", e); return null; } XMethod xmethod = xclass.findMethod(mName, sig, isStatic); if (xmethod == null) xmethod = XFactory.createXMethod(cName, mName, sig, isStatic); return xmethod; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addMethodAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, edu.umd.cs.findbugs.ba.NullnessAnnotation) */ public void addMethodAnnotation( String cName, String mName, String sig, boolean isStatic, NullnessAnnotation annotation) { if (DEBUG) { System.out.println( "addMethodAnnotation: annotate " + cName + "." + mName + " with " + annotation); } XMethod xmethod = getXMethod(cName, mName, sig, isStatic); if (xmethod == null) return; // Get JSR-305 nullness annotation type ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation); // Create an AnnotationValue AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType); // Destructively add the annotation to the MethodInfo object xmethod.addAnnotation(annotationValue); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#addMethodParameterAnnotation(java.lang.String, java.lang.String, java.lang.String, boolean, int, edu.umd.cs.findbugs.ba.NullnessAnnotation) */ public void addMethodParameterAnnotation( @DottedClassName String cName, String mName, String sig, boolean isStatic, int param, NullnessAnnotation annotation) { if (DEBUG) { System.out.println( "addMethodParameterAnnotation: annotate " + cName + "." + mName + " param " + param + " with " + annotation); } XMethod xmethod = getXMethod(cName, mName, sig, isStatic); if (xmethod == null) return; // Get JSR-305 nullness annotation type ClassDescriptor nullnessAnnotationType = getNullnessAnnotationClassDescriptor(annotation); // Create an AnnotationValue AnnotationValue annotationValue = new AnnotationValue(nullnessAnnotationType); if (!xmethod.getClassName().equals(cName)) { if (false) AnalysisContext.logError( "Could not fully resolve method " + cName + "." + mName + sig + " to apply annotation " + annotation); return; } // Destructively add the annotation to the MethodInfo object xmethod.addParameterAnnotation(param, annotationValue); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase#loadAuxiliaryAnnotations() */ public void loadAuxiliaryAnnotations() { DefaultNullnessAnnotations.addDefaultNullnessAnnotations(this); } /** * Convert a Nonnull-based TypeQualifierAnnotation into a NullnessAnnotation. * * @param tqa Nonnull-based TypeQualifierAnnotation * @return corresponding NullnessAnnotation */ private NullnessAnnotation toNullnessAnnotation(@CheckForNull TypeQualifierAnnotation tqa) { if (tqa == null) { return null; } switch (tqa.when) { case ALWAYS: return NullnessAnnotation.NONNULL; case MAYBE: return NullnessAnnotation.CHECK_FOR_NULL; case NEVER: return NullnessAnnotation.CHECK_FOR_NULL; case UNKNOWN: return NullnessAnnotation.UNKNOWN_NULLNESS; } throw new IllegalStateException(); } }
/** * Analysis to determine where particular values are locked in a method. The dataflow values are * maps of value numbers to the number of times those values are locked. * * @author David Hovemeyer * @see ValueNumberAnalysis */ public class LockAnalysis extends ForwardDataflowAnalysis<LockSet> { private static final boolean DEBUG = SystemProperties.getBoolean("la.debug"); private final MethodGen methodGen; private final ValueNumberDataflow vnaDataflow; private final ValueNumberAnalysis vna; private final boolean isSynchronized; private final boolean isStatic; public LockAnalysis(MethodGen methodGen, ValueNumberDataflow vnaDataflow, DepthFirstSearch dfs) { super(dfs); this.methodGen = methodGen; this.vnaDataflow = vnaDataflow; this.vna = vnaDataflow.getAnalysis(); this.isSynchronized = methodGen.isSynchronized(); this.isStatic = methodGen.isStatic(); if (DEBUG) { System.out.println( "Analyzing Locks in " + methodGen.getClassName() + "." + methodGen.getName()); } } @Override public LockSet createFact() { return new LockSet(); } @Override public void copy(LockSet source, LockSet dest) { dest.copyFrom(source); } @Override public void initEntryFact(LockSet result) { result.clear(); result.setDefaultLockCount(0); if (isSynchronized && !isStatic) { ValueNumber thisValue = vna.getThisValue(); result.setLockCount(thisValue.getNumber(), 1); } else if (isSynchronized && isStatic) { ValueNumber thisValue = vna.getClassObjectValue(methodGen.getClassName()); result.setLockCount(thisValue.getNumber(), 1); } } @Override public void makeFactTop(LockSet fact) { fact.clear(); fact.setDefaultLockCount(LockSet.TOP); } @Override public boolean isTop(LockSet fact) { return fact.isTop(); } @Override public boolean same(LockSet fact1, LockSet fact2) { return fact1.sameAs(fact2); } @Override public void meetInto(LockSet fact, Edge edge, LockSet result) throws DataflowAnalysisException { result.meetWith(fact); } @Override public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, LockSet fact) throws DataflowAnalysisException { Instruction ins = handle.getInstruction(); short opcode = ins.getOpcode(); if (opcode == Constants.MONITORENTER || opcode == Constants.MONITOREXIT) { ValueNumberFrame frame = vnaDataflow.getFactAtLocation(new Location(handle, basicBlock)); modifyLock(frame, fact, opcode == Constants.MONITORENTER ? 1 : -1); } else if (opcode == Constants.INVOKEVIRTUAL || opcode == Constants.INVOKEINTERFACE) { InvokeInstruction inv = (InvokeInstruction) ins; String name = inv.getMethodName(methodGen.getConstantPool()); String sig = inv.getSignature(methodGen.getConstantPool()); ValueNumberFrame frame = vnaDataflow.getFactAtLocation(new Location(handle, basicBlock)); if ("()V".equals(sig) && ("lock".equals(name) || "lockInterruptibly".equals(name))) { modifyLock(frame, fact, 1); } else if ("()V".equals(sig) && ("unlock".equals(name))) { modifyLock(frame, fact, -1); } } else if ((ins instanceof ReturnInstruction) && isSynchronized && !isStatic) { lockOp(fact, vna.getThisValue().getNumber(), -1); } } private void modifyLock(ValueNumberFrame frame, LockSet fact, int delta) throws DataflowAnalysisException { if (frame.isValid()) { int lockNumber = frame.getTopValue().getNumber(); lockOp(fact, lockNumber, delta); } } private void lockOp(LockSet fact, int lockNumber, int delta) { int value = fact.getLockCount(lockNumber); if (value < 0) { return; } value += delta; if (value < 0) { value = LockSet.BOTTOM; } if (DEBUG) { System.out.println( "Setting " + lockNumber + " to " + value + " in " + methodGen.getClassName() + "." + methodGen.getName()); } fact.setLockCount(lockNumber, value); } @Override public boolean isFactValid(LockSet fact) { return true; } // public static void main(String[] argv) throws Exception { // if (argv.length != 1) { // System.err.println("Usage: " + LockAnalysis.class.getName() + // " <classfile>"); // System.exit(1); // } // // DataflowTestDriver<LockSet, LockAnalysis> driver = new // DataflowTestDriver<LockSet, LockAnalysis>() { // @Override // public Dataflow<LockSet, LockAnalysis> createDataflow(ClassContext // classContext, Method method) // throws CFGBuilderException, DataflowAnalysisException { // return classContext.getLockDataflow(method); // } // }; // // driver.execute(argv[0]); // } }
public class FindFinalizeInvocations extends BytecodeScanningDetector implements StatelessDetector { private static final boolean DEBUG = SystemProperties.getBoolean("ffi.debug"); private BugReporter bugReporter; private final BugAccumulator bugAccumulator; public FindFinalizeInvocations(BugReporter bugReporter) { this.bugReporter = bugReporter; this.bugAccumulator = new BugAccumulator(bugReporter); } boolean sawSuperFinalize; @Override public void visit(Method obj) { if (DEBUG) System.out.println("FFI: visiting " + getFullyQualifiedMethodName()); if (getMethodName().equals("finalize") && getMethodSig().equals("()V") && (obj.getAccessFlags() & (ACC_PUBLIC)) != 0) bugReporter.reportBug( new BugInstance(this, "FI_PUBLIC_SHOULD_BE_PROTECTED", NORMAL_PRIORITY) .addClassAndMethod(this)); } @Override public void visit(Code obj) { sawSuperFinalize = false; super.visit(obj); bugAccumulator.reportAccumulatedBugs(); if (!getMethodName().equals("finalize") || !getMethodSig().equals("()V")) return; String overridesFinalizeIn = Lookup.findSuperImplementor(getDottedClassName(), "finalize", "()V", bugReporter); boolean superHasNoFinalizer = overridesFinalizeIn.equals("java.lang.Object"); // System.out.println("superclass: " + superclassName); if (obj.getCode().length == 1) { if (superHasNoFinalizer) { if (!getMethod().isFinal()) bugReporter.reportBug( new BugInstance(this, "FI_EMPTY", NORMAL_PRIORITY).addClassAndMethod(this)); } else bugReporter.reportBug( new BugInstance(this, "FI_NULLIFY_SUPER", NORMAL_PRIORITY) .addClassAndMethod(this) .addClass(overridesFinalizeIn)); } else if (obj.getCode().length == 5 && sawSuperFinalize) bugReporter.reportBug( new BugInstance(this, "FI_USELESS", NORMAL_PRIORITY).addClassAndMethod(this)); else if (!sawSuperFinalize && !superHasNoFinalizer) bugReporter.reportBug( new BugInstance(this, "FI_MISSING_SUPER_CALL", NORMAL_PRIORITY) .addClassAndMethod(this) .addClass(overridesFinalizeIn)); } @Override public void sawOpcode(int seen) { if (seen == INVOKEVIRTUAL && getNameConstantOperand().equals("finalize") && getSigConstantOperand().equals("()V")) { bugAccumulator.accumulateBug( new BugInstance( this, "FI_EXPLICIT_INVOCATION", getMethodName().equals("finalize") && getMethodSig().equals("()V") ? HIGH_PRIORITY : NORMAL_PRIORITY) .addClassAndMethod(this) .addCalledMethod(this), this); } if (seen == INVOKESPECIAL && getNameConstantOperand().equals("finalize")) sawSuperFinalize = true; } }
/** * Facade for class hierarchy queries. These typically access the class hierarchy using the {@link * org.apache.bcel.Repository} class. Callers should generally expect to handle * ClassNotFoundException for when referenced classes can't be found. * * @author David Hovemeyer */ public class Hierarchy { protected static final boolean DEBUG_METHOD_LOOKUP = SystemProperties.getBoolean("hier.lookup.debug"); /** Type of java.lang.Exception. */ public static final ObjectType EXCEPTION_TYPE = ObjectTypeFactory.getInstance("java.lang.Exception"); /** Type of java.lang.Error. */ public static final ObjectType ERROR_TYPE = ObjectTypeFactory.getInstance("java.lang.Error"); /** Type of java.lang.RuntimeException. */ public static final ObjectType RUNTIME_EXCEPTION_TYPE = ObjectTypeFactory.getInstance("java.lang.RuntimeException"); /** * Determine whether one class (or reference type) is a subtype of another. * * @param clsName the name of the class or reference type * @param possibleSupertypeClassName the name of the possible superclass * @return true if clsName is a subtype of possibleSupertypeClassName, false if not */ public static boolean isSubtype(String clsName, String possibleSupertypeClassName) throws ClassNotFoundException { ObjectType cls = ObjectTypeFactory.getInstance(clsName); ObjectType superCls = ObjectTypeFactory.getInstance(possibleSupertypeClassName); return isSubtype(cls, superCls); } /** * Determine if one reference type is a subtype of another. * * @param t a reference type * @param possibleSupertype the possible supertype * @return true if t is a subtype of possibleSupertype, false if not */ public static boolean isSubtype(ReferenceType t, ReferenceType possibleSupertype) throws ClassNotFoundException { if (Subtypes2.ENABLE_SUBTYPES2) { return Global.getAnalysisCache().getDatabase(Subtypes2.class).isSubtype(t, possibleSupertype); } else { Map<ReferenceType, Boolean> subtypes = subtypeCache.get(possibleSupertype); if (subtypes == null) { subtypes = new HashMap<ReferenceType, Boolean>(); subtypeCache.put(possibleSupertype, subtypes); } Boolean result = subtypes.get(t); if (result == null) { result = Boolean.valueOf(t.isAssignmentCompatibleWith(possibleSupertype)); subtypes.put(t, result); } return result; } } static Map<ReferenceType, Map<ReferenceType, Boolean>> subtypeCache = new HashMap<ReferenceType, Map<ReferenceType, Boolean>>(); /** * Determine if the given ObjectType reference represents a <em>universal</em> exception handler. * That is, one that will catch any kind of exception. * * @param catchType the ObjectType of the exception handler * @return true if catchType is null, or if catchType is java.lang.Throwable */ public static boolean isUniversalExceptionHandler(ObjectType catchType) { return catchType == null || catchType.equals(Type.THROWABLE); } /** * Determine if the given ObjectType refers to an unchecked exception (RuntimeException or Error). */ public static boolean isUncheckedException(ObjectType type) throws ClassNotFoundException { return isSubtype(type, RUNTIME_EXCEPTION_TYPE) || isSubtype(type, ERROR_TYPE) || type.equals(Type.THROWABLE); } /** * Determine if method whose name and signature is specified is a monitor wait operation. * * @param methodName name of the method * @param methodSig signature of the method * @return true if the method is a monitor wait, false if not */ public static boolean isMonitorWait(String methodName, String methodSig) { return methodName.equals("wait") && (methodSig.equals("()V") || methodSig.equals("(J)V") || methodSig.equals("(JI)V")); } /** * Determine if given Instruction is a monitor wait. * * @param ins the Instruction * @param cpg the ConstantPoolGen for the Instruction * @return true if the instruction is a monitor wait, false if not */ public static boolean isMonitorWait(Instruction ins, ConstantPoolGen cpg) { if (!(ins instanceof InvokeInstruction)) return false; if (ins.getOpcode() == Constants.INVOKESTATIC) return false; InvokeInstruction inv = (InvokeInstruction) ins; String methodName = inv.getMethodName(cpg); String methodSig = inv.getSignature(cpg); return isMonitorWait(methodName, methodSig); } /** * Determine if method whose name and signature is specified is a monitor notify operation. * * @param methodName name of the method * @param methodSig signature of the method * @return true if the method is a monitor notify, false if not */ public static boolean isMonitorNotify(String methodName, String methodSig) { return (methodName.equals("notify") || methodName.equals("notifyAll")) && methodSig.equals("()V"); } /** * Determine if given Instruction is a monitor wait. * * @param ins the Instruction * @param cpg the ConstantPoolGen for the Instruction * @return true if the instruction is a monitor wait, false if not */ public static boolean isMonitorNotify(Instruction ins, ConstantPoolGen cpg) { if (!(ins instanceof InvokeInstruction)) return false; if (ins.getOpcode() == Constants.INVOKESTATIC) return false; InvokeInstruction inv = (InvokeInstruction) ins; String methodName = inv.getMethodName(cpg); String methodSig = inv.getSignature(cpg); return isMonitorNotify(methodName, methodSig); } /** * Look up the method referenced by given InvokeInstruction. This method does <em>not</em> look * for implementations in super or subclasses according to the virtual dispatch rules. * * @param inv the InvokeInstruction * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to * @return the JavaClassAndMethod, or null if no such method is defined in the class */ public static JavaClassAndMethod findExactMethod(InvokeInstruction inv, ConstantPoolGen cpg) throws ClassNotFoundException { return findExactMethod(inv, cpg, ANY_METHOD); } /** * Look up the method referenced by given InvokeInstruction. This method does <em>not</em> look * for implementations in super or subclasses according to the virtual dispatch rules. * * @param inv the InvokeInstruction * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to * @param chooser JavaClassAndMethodChooser to use to pick the method from among the candidates * @return the JavaClassAndMethod, or null if no such method is defined in the class */ public static JavaClassAndMethod findExactMethod( InvokeInstruction inv, ConstantPoolGen cpg, JavaClassAndMethodChooser chooser) throws ClassNotFoundException { String className = inv.getClassName(cpg); String methodName = inv.getName(cpg); String methodSig = inv.getSignature(cpg); JavaClass jclass = Repository.lookupClass(className); return findMethod(jclass, methodName, methodSig, chooser); } /** * Visit all superclass methods which the given method overrides. * * @param method the method * @param chooser chooser which visits each superclass method * @return the chosen method, or null if no method is chosen * @throws ClassNotFoundException */ public static JavaClassAndMethod visitSuperClassMethods( JavaClassAndMethod method, JavaClassAndMethodChooser chooser) throws ClassNotFoundException { return findMethod( method.getJavaClass().getSuperClasses(), method.getMethod().getName(), method.getMethod().getSignature(), chooser); } /** * Visit all superinterface methods which the given method implements. * * @param method the method * @param chooser chooser which visits each superinterface method * @return the chosen method, or null if no method is chosen * @throws ClassNotFoundException */ public static JavaClassAndMethod visitSuperInterfaceMethods( JavaClassAndMethod method, JavaClassAndMethodChooser chooser) throws ClassNotFoundException { return findMethod( method.getJavaClass().getAllInterfaces(), method.getMethod().getName(), method.getMethod().getSignature(), chooser); } /** * Find the least upper bound method in the class hierarchy which could be called by the given * InvokeInstruction. One reason this method is useful is that it indicates which declared * exceptions are thrown by the called methods. * * <p> * * <ul> * <li>For invokespecial, this is simply an exact lookup. * <li>For invokestatic and invokevirtual, the named class is searched, followed by superclasses * up to the root of the object hierarchy (java.lang.Object). Yes, invokestatic really is * declared to check superclasses. See VMSpec, 2nd ed, sec. 5.4.3.3. * <li>For invokeinterface, the named class is searched, followed by all interfaces transitively * declared by the class. (Question: is the order important here? Maybe the VM spec requires * that the actual interface desired is given, so the extended lookup will not be required. * Should check.) * </ul> * * @param inv the InvokeInstruction * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to * @return the JavaClassAndMethod, or null if no matching method can be found */ public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound( InvokeInstruction inv, ConstantPoolGen cpg) throws ClassNotFoundException { return findInvocationLeastUpperBound(inv, cpg, ANY_METHOD); } public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound( InvokeInstruction inv, ConstantPoolGen cpg, JavaClassAndMethodChooser methodChooser) throws ClassNotFoundException { if (DEBUG_METHOD_LOOKUP) { System.out.println( "Find prototype method for " + SignatureConverter.convertMethodSignature(inv, cpg)); } short opcode = inv.getOpcode(); if (opcode == Constants.INVOKESTATIC) { if (methodChooser == INSTANCE_METHOD) return null; } else { if (methodChooser == STATIC_METHOD) return null; } // Find the method if (opcode == Constants.INVOKESPECIAL) { // Non-virtual dispatch return findExactMethod(inv, cpg, methodChooser); } else { String className = inv.getClassName(cpg); String methodName = inv.getName(cpg); String methodSig = inv.getSignature(cpg); if (DEBUG_METHOD_LOOKUP) { System.out.println("[Class name is " + className + "]"); System.out.println("[Method name is " + methodName + "]"); System.out.println("[Method signature is " + methodSig + "]"); } if (className.startsWith("[")) { // Java 1.5 allows array classes to appear as the class name className = "java.lang.Object"; } JavaClass jClass = Repository.lookupClass(className); return findInvocationLeastUpperBound( jClass, methodName, methodSig, methodChooser, opcode == Constants.INVOKEINTERFACE); } } public static @CheckForNull JavaClassAndMethod findInvocationLeastUpperBound( JavaClass jClass, String methodName, String methodSig, JavaClassAndMethodChooser methodChooser, boolean invokeInterface) throws ClassNotFoundException { JavaClassAndMethod result = findMethod(jClass, methodName, methodSig, methodChooser); if (result != null) return result; if (invokeInterface) for (JavaClass i : jClass.getInterfaces()) { result = findInvocationLeastUpperBound(i, methodName, methodSig, methodChooser, invokeInterface); if (result != null) return null; } else { JavaClass sClass = jClass.getSuperClass(); if (sClass != null) return findInvocationLeastUpperBound( sClass, methodName, methodSig, methodChooser, invokeInterface); } return null; } /** * Find the declared exceptions for the method called by given instruction. * * @param inv the InvokeInstruction * @param cpg the ConstantPoolGen used by the class the InvokeInstruction belongs to * @return array of ObjectTypes of thrown exceptions, or null if we can't find the list of * declared exceptions * @deprecated Use {@link Hierarchy2#findDeclaredExceptions(InvokeInstruction,ConstantPoolGen)} * instead */ @Deprecated public static ObjectType[] findDeclaredExceptions(InvokeInstruction inv, ConstantPoolGen cpg) throws ClassNotFoundException { return Hierarchy2.findDeclaredExceptions(inv, cpg); } /** * Find a method in given class. * * @param javaClass the class * @param methodName the name of the method * @param methodSig the signature of the method * @return the JavaClassAndMethod, or null if no such method exists in the class */ public static @CheckForNull JavaClassAndMethod findMethod( JavaClass javaClass, String methodName, String methodSig) { return findMethod(javaClass, methodName, methodSig, ANY_METHOD); } public static @CheckForNull JavaClassAndMethod findMethod( JavaClass javaClass, String methodName, String methodSig, JavaClassAndMethodChooser chooser) { if (DEBUG_METHOD_LOOKUP) { System.out.println("Check " + javaClass.getClassName()); } Method[] methodList = javaClass.getMethods(); for (Method method : methodList) if (method.getName().equals(methodName) && method.getSignature().equals(methodSig)) { JavaClassAndMethod m = new JavaClassAndMethod(javaClass, method); if (chooser.choose(m)) { if (DEBUG_METHOD_LOOKUP) { System.out.println("\t==> FOUND: " + method); } return m; } } if (DEBUG_METHOD_LOOKUP) { System.out.println("\t==> NOT FOUND"); } return null; } /** * Find a method in given class. * * @param classDesc the class descriptor * @param methodName the name of the method * @param methodSig the signature of the method * @param isStatic are we looking for a static method? * @return the JavaClassAndMethod, or null if no such method exists in the class */ public static @CheckForNull XMethod findMethod( ClassDescriptor classDesc, String methodName, String methodSig, boolean isStatic) { if (DEBUG_METHOD_LOOKUP) { System.out.println("Check " + classDesc.getClassName()); } try { XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, classDesc); return xClass.findMethod(methodName, methodSig, isStatic); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Error looking for " + classDesc + "." + methodName + methodSig, e); return null; } } /** * Find a method in given class. * * @param javaClass the class * @param methodName the name of the method * @param methodSig the signature of the method * @return the JavaClassAndMethod, or null if no such method exists in the class */ @Deprecated public static @CheckForNull JavaClassAndMethod findConcreteMethod( JavaClass javaClass, String methodName, String methodSig) { if (DEBUG_METHOD_LOOKUP) { System.out.println("Check " + javaClass.getClassName()); } Method[] methodList = javaClass.getMethods(); for (Method method : methodList) if (method.getName().equals(methodName) && method.getSignature().equals(methodSig) && accessFlagsAreConcrete(method.getAccessFlags())) { JavaClassAndMethod m = new JavaClassAndMethod(javaClass, method); return m; } if (DEBUG_METHOD_LOOKUP) { System.out.println("\t==> NOT FOUND"); } return null; } /** * Find a method in given class. * * @param javaClass the class * @param methodName the name of the method * @param methodSig the signature of the method * @param chooser the JavaClassAndMethodChooser to use to screen possible candidates * @return the XMethod, or null if no such method exists in the class */ @Deprecated public static @CheckForNull XMethod findXMethod( JavaClass javaClass, String methodName, String methodSig, JavaClassAndMethodChooser chooser) { JavaClassAndMethod result = findMethod(javaClass, methodName, methodSig, chooser); return result == null ? null : XFactory.createXMethod(result.getJavaClass(), result.getMethod()); } /** JavaClassAndMethodChooser which accepts any method. */ public static final JavaClassAndMethodChooser ANY_METHOD = new JavaClassAndMethodChooser() { public boolean choose(JavaClassAndMethod javaClassAndMethod) { return true; } public boolean choose(XMethod method) { return true; } }; // FIXME: perhaps native methods should be concrete. public static boolean accessFlagsAreConcrete(int accessFlags) { return (accessFlags & Constants.ACC_ABSTRACT) == 0 && (accessFlags & Constants.ACC_NATIVE) == 0; } /** JavaClassAndMethodChooser which accepts only concrete (not abstract or native) methods. */ public static final JavaClassAndMethodChooser CONCRETE_METHOD = new JavaClassAndMethodChooser() { public boolean choose(JavaClassAndMethod javaClassAndMethod) { Method method = javaClassAndMethod.getMethod(); int accessFlags = method.getAccessFlags(); return accessFlagsAreConcrete(accessFlags); } public boolean choose(XMethod method) { return accessFlagsAreConcrete(method.getAccessFlags()); } }; /** JavaClassAndMethodChooser which accepts only static methods. */ public static final JavaClassAndMethodChooser STATIC_METHOD = new JavaClassAndMethodChooser() { public boolean choose(JavaClassAndMethod javaClassAndMethod) { return javaClassAndMethod.getMethod().isStatic(); } public boolean choose(XMethod method) { return method.isStatic(); } }; /** JavaClassAndMethodChooser which accepts only instance methods. */ public static final JavaClassAndMethodChooser INSTANCE_METHOD = new JavaClassAndMethodChooser() { public boolean choose(JavaClassAndMethod javaClassAndMethod) { return !javaClassAndMethod.getMethod().isStatic(); } public boolean choose(XMethod method) { return !method.isStatic(); } }; /** * Find a method in given list of classes, searching the classes in order. * * @param classList list of classes in which to search * @param methodName the name of the method * @param methodSig the signature of the method * @return the JavaClassAndMethod, or null if no such method exists in the class */ @Deprecated public static JavaClassAndMethod findMethod( JavaClass[] classList, String methodName, String methodSig) { return findMethod(classList, methodName, methodSig, ANY_METHOD); } /** * Find a method in given list of classes, searching the classes in order. * * @param classList list of classes in which to search * @param methodName the name of the method * @param methodSig the signature of the method * @param chooser JavaClassAndMethodChooser to select which methods are considered; it must return * true for a method to be returned * @return the JavaClassAndMethod, or null if no such method exists in the class */ public static JavaClassAndMethod findMethod( JavaClass[] classList, String methodName, String methodSig, JavaClassAndMethodChooser chooser) { JavaClassAndMethod m = null; for (JavaClass cls : classList) { if ((m = findMethod(cls, methodName, methodSig, chooser)) != null) break; } return m; } /** * Find XMethod for method in given list of classes, searching the classes in order. * * @param classList list of classes in which to search * @param methodName the name of the method * @param methodSig the signature of the method * @return the XMethod, or null if no such method exists in the class */ @Deprecated public static XMethod findXMethod(JavaClass[] classList, String methodName, String methodSig) { return findXMethod(classList, methodName, methodSig, ANY_METHOD); } /** * Find XMethod for method in given list of classes, searching the classes in order. * * @param classList list of classes in which to search * @param methodName the name of the method * @param methodSig the signature of the method * @param chooser JavaClassAndMethodChooser to select which methods are considered; it must return * true for a method to be returned * @return the XMethod, or null if no such method exists in the class */ @Deprecated public static XMethod findXMethod( JavaClass[] classList, String methodName, String methodSig, JavaClassAndMethodChooser chooser) { for (JavaClass cls : classList) { JavaClassAndMethod m; if ((m = findMethod(cls, methodName, methodSig)) != null && chooser.choose(m)) { return XFactory.createXMethod(cls, m.getMethod()); } } return null; } /** * Resolve possible method call targets. This works for both static and instance method calls. * * @param invokeInstruction the InvokeInstruction * @param typeFrame the TypeFrame containing the types of stack values * @param cpg the ConstantPoolGen * @return Set of methods which might be called * @throws DataflowAnalysisException * @throws ClassNotFoundException */ public static Set<JavaClassAndMethod> resolveMethodCallTargets( InvokeInstruction invokeInstruction, TypeFrame typeFrame, ConstantPoolGen cpg) throws DataflowAnalysisException, ClassNotFoundException { short opcode = invokeInstruction.getOpcode(); if (opcode == Constants.INVOKESTATIC) { HashSet<JavaClassAndMethod> result = new HashSet<JavaClassAndMethod>(); JavaClassAndMethod targetMethod = findInvocationLeastUpperBound(invokeInstruction, cpg, CONCRETE_METHOD); if (targetMethod != null) { result.add(targetMethod); } return result; } if (!typeFrame.isValid()) { return new HashSet<JavaClassAndMethod>(); } Type receiverType; boolean receiverTypeIsExact; if (opcode == Constants.INVOKESPECIAL) { // invokespecial instructions are dispatched to EXACTLY // the class specified by the instruction receiverType = ObjectTypeFactory.getInstance(invokeInstruction.getClassName(cpg)); receiverTypeIsExact = false; // Doesn't actually matter } else { // For invokevirtual and invokeinterface instructions, we have // virtual dispatch. By taking the receiver type (which may be a // subtype of the class specified by the instruction), // we may get a more precise set of call targets. int instanceStackLocation = typeFrame.getInstanceStackLocation(invokeInstruction, cpg); receiverType = typeFrame.getStackValue(instanceStackLocation); if (!(receiverType instanceof ReferenceType)) { return new HashSet<JavaClassAndMethod>(); } receiverTypeIsExact = typeFrame.isExact(instanceStackLocation); } if (DEBUG_METHOD_LOOKUP) { System.out.println( "[receiver type is " + receiverType + ", " + (receiverTypeIsExact ? "exact]" : " not exact]")); } return resolveMethodCallTargets( (ReferenceType) receiverType, invokeInstruction, cpg, receiverTypeIsExact); } /** * Resolve possible instance method call targets. Assumes that invokevirtual and invokeinterface * methods may call any subtype of the receiver class. * * @param receiverType type of the receiver object * @param invokeInstruction the InvokeInstruction * @param cpg the ConstantPoolGen * @return Set of methods which might be called * @throws ClassNotFoundException */ public static Set<JavaClassAndMethod> resolveMethodCallTargets( ReferenceType receiverType, InvokeInstruction invokeInstruction, ConstantPoolGen cpg) throws ClassNotFoundException { return resolveMethodCallTargets(receiverType, invokeInstruction, cpg, false); } /** * Resolve possible instance method call targets. * * @param receiverType type of the receiver object * @param invokeInstruction the InvokeInstruction * @param cpg the ConstantPoolGen * @param receiverTypeIsExact if true, the receiver type is known exactly, which should allow a * precise result * @return Set of methods which might be called * @throws ClassNotFoundException */ public static Set<JavaClassAndMethod> resolveMethodCallTargets( ReferenceType receiverType, InvokeInstruction invokeInstruction, ConstantPoolGen cpg, boolean receiverTypeIsExact) throws ClassNotFoundException { HashSet<JavaClassAndMethod> result = new HashSet<JavaClassAndMethod>(); if (invokeInstruction.getOpcode() == Constants.INVOKESTATIC) throw new IllegalArgumentException(); String methodName = invokeInstruction.getName(cpg); String methodSig = invokeInstruction.getSignature(cpg); // Array method calls aren't virtual. // They should just resolve to Object methods. if (receiverType instanceof ArrayType) { JavaClass javaLangObject = AnalysisContext.currentAnalysisContext().lookupClass("java.lang.Object"); JavaClassAndMethod classAndMethod = findMethod(javaLangObject, methodName, methodSig, INSTANCE_METHOD); if (classAndMethod != null) result.add(classAndMethod); return result; } if (receiverType instanceof NullType) { return Collections.<JavaClassAndMethod>emptySet(); } AnalysisContext analysisContext = AnalysisContext.currentAnalysisContext(); // Get the receiver class. String receiverClassName = ((ObjectType) receiverType).getClassName(); JavaClass receiverClass = analysisContext.lookupClass(receiverClassName); ClassDescriptor receiverDesc = DescriptorFactory.createClassDescriptorFromDottedClassName(receiverClassName); // Figure out the upper bound for the method. // This is what will be called if this is not a virtual call site. JavaClassAndMethod upperBound = findMethod(receiverClass, methodName, methodSig, CONCRETE_METHOD); if (upperBound == null) { upperBound = findInvocationLeastUpperBound( receiverClass, methodName, methodSig, CONCRETE_METHOD, false); } if (upperBound != null) { if (DEBUG_METHOD_LOOKUP) { System.out.println( "Adding upper bound: " + SignatureConverter.convertMethodSignature( upperBound.getJavaClass(), upperBound.getMethod())); } result.add(upperBound); } // Is this a virtual call site? boolean virtualCall = (invokeInstruction.getOpcode() == Constants.INVOKEVIRTUAL || invokeInstruction.getOpcode() == Constants.INVOKEINTERFACE) && (upperBound == null || !upperBound.getJavaClass().isFinal() && !upperBound.getMethod().isFinal()) && !receiverTypeIsExact; if (virtualCall) { if (!receiverClassName.equals("java.lang.Object")) { // This is a true virtual call: assume that any concrete // subtype method may be called. Set<ClassDescriptor> subTypeSet = analysisContext.getSubtypes2().getSubtypes(receiverDesc); for (ClassDescriptor subtype : subTypeSet) { XMethod concreteSubtypeMethod = findMethod(subtype, methodName, methodSig, false); if (concreteSubtypeMethod != null && (concreteSubtypeMethod.getAccessFlags() & Constants.ACC_ABSTRACT) == 0) { result.add(new JavaClassAndMethod(concreteSubtypeMethod)); } } if (false && subTypeSet.size() > 500) new RuntimeException( receiverClassName + " has " + subTypeSet.size() + " subclasses, " + result.size() + " of which implement " + methodName + methodSig + " " + invokeInstruction) .printStackTrace(System.out); } } return result; } /** * Return whether or not the given method is concrete. * * @param xmethod the method * @return true if the method is concrete, false otherwise */ @Deprecated public static boolean isConcrete(XMethod xmethod) { int accessFlags = xmethod.getAccessFlags(); return (accessFlags & Constants.ACC_ABSTRACT) == 0 && (accessFlags & Constants.ACC_NATIVE) == 0; } /** * Find a field with given name defined in given class. * * @param className the name of the class * @param fieldName the name of the field * @return the Field, or null if no such field could be found */ public static Field findField(String className, String fieldName) throws ClassNotFoundException { JavaClass jclass = Repository.lookupClass(className); while (jclass != null) { Field[] fieldList = jclass.getFields(); for (Field field : fieldList) { if (field.getName().equals(fieldName)) { return field; } } jclass = jclass.getSuperClass(); } return null; } /** * Look up a field with given name and signature in given class, returning it as an {@link XField * XField} object. If a field can't be found in the immediate class, its superclass is search, and * so forth. * * @param className name of the class through which the field is referenced * @param fieldName name of the field * @param fieldSig signature of the field * @param isStatic true if field is static, false otherwise * @return an XField object representing the field, or null if no such field could be found */ public static XField findXField( String className, String fieldName, String fieldSig, boolean isStatic) throws ClassNotFoundException { return XFactory.createXField(className, fieldName, fieldSig, isStatic); } /** * Look up the field referenced by given FieldInstruction, returning it as an {@link XField * XField} object. * * @param fins the FieldInstruction * @param cpg the ConstantPoolGen used by the class containing the instruction * @return an XField object representing the field, or null if no such field could be found */ public static XField findXField(FieldInstruction fins, @NonNull ConstantPoolGen cpg) throws ClassNotFoundException { String className = fins.getClassName(cpg); String fieldName = fins.getFieldName(cpg); String fieldSig = fins.getSignature(cpg); boolean isStatic = (fins.getOpcode() == Constants.GETSTATIC || fins.getOpcode() == Constants.PUTSTATIC); XField xfield = findXField(className, fieldName, fieldSig, isStatic); short opcode = fins.getOpcode(); if (xfield != null && xfield.isResolved() && xfield.isStatic() == (opcode == Constants.GETSTATIC || opcode == Constants.PUTSTATIC)) return xfield; else return null; } /** * Determine whether the given INVOKESTATIC instruction is an inner-class field accessor method. * * @param inv the INVOKESTATIC instruction * @param cpg the ConstantPoolGen for the method * @return true if the instruction is an inner-class field accessor, false if not */ public static boolean isInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg) { String methodName = inv.getName(cpg); return methodName.startsWith("access$"); } /** * Get the InnerClassAccess for access method called by given INVOKESTATIC. * * @param inv the INVOKESTATIC instruction * @param cpg the ConstantPoolGen for the method * @return the InnerClassAccess, or null if the instruction is not an inner-class access */ public static InnerClassAccess getInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg) throws ClassNotFoundException { String className = inv.getClassName(cpg); String methodName = inv.getName(cpg); String methodSig = inv.getSignature(cpg); InnerClassAccess access = AnalysisContext.currentAnalysisContext() .getInnerClassAccessMap() .getInnerClassAccess(className, methodName); return (access != null && access.getMethodSignature().equals(methodSig)) ? access : null; } }
/** * Dataflow analysis to find values unconditionally dereferenced in the future. * * @author David Hovemeyer */ public class UnconditionalValueDerefAnalysis extends BackwardDataflowAnalysis<UnconditionalValueDerefSet> { public static final boolean DEBUG = SystemProperties.getBoolean("fnd.derefs.debug"); public static final boolean ASSUME_NONZERO_TRIP_LOOPS = SystemProperties.getBoolean("fnd.derefs.nonzerotrip"); public static final boolean IGNORE_DEREF_OF_NCP = SystemProperties.getBoolean("fnd.derefs.ignoreNCP", false); public static final boolean CHECK_ANNOTATIONS = SystemProperties.getBoolean("fnd.derefs.checkannotations", true); public static final boolean CHECK_CALLS = SystemProperties.getBoolean("fnd.derefs.checkcalls", true); public static final boolean DEBUG_CHECK_CALLS = SystemProperties.getBoolean("fnd.derefs.checkcalls.debug"); private static final int NULLCHECK1[] = {Opcodes.DUP, Opcodes.INVOKESPECIAL, Opcodes.ATHROW}; private static final int NULLCHECK2[] = { Opcodes.DUP, Opcodes.LDC, Opcodes.INVOKESPECIAL, Opcodes.ATHROW }; private final CFG cfg; private final Method method; private final MethodGen methodGen; private final ValueNumberDataflow vnaDataflow; private final AssertionMethods assertionMethods; private IsNullValueDataflow invDataflow; private TypeDataflow typeDataflow; /** * Constructor. * * @param rdfs the reverse depth-first-search (for the block order) * @param cfg the CFG for the method * @param methodGen the MethodGen for the method * @param vnaDataflow * @param assertionMethods AssertionMethods for the analyzed class */ public UnconditionalValueDerefAnalysis( ReverseDepthFirstSearch rdfs, DepthFirstSearch dfs, CFG cfg, Method method, MethodGen methodGen, ValueNumberDataflow vnaDataflow, AssertionMethods assertionMethods) { super(rdfs, dfs); this.cfg = cfg; this.methodGen = methodGen; this.method = method; this.vnaDataflow = vnaDataflow; this.assertionMethods = assertionMethods; if (DEBUG) { System.out.println( "UnconditionalValueDerefAnalysis analysis " + methodGen.getClassName() + "." + methodGen.getName() + " : " + methodGen.getSignature()); } } @Override public String toString() { return this.getClass().getSimpleName() + " of " + method; } /** * HACK: use the given is-null dataflow to clear deref sets for values that are known to be * definitely non-null on a branch. * * @param invDataflow the IsNullValueDataflow to use */ public void clearDerefsOnNonNullBranches(IsNullValueDataflow invDataflow) { this.invDataflow = invDataflow; } public void setTypeDataflow(TypeDataflow typeDataflow) { this.typeDataflow = typeDataflow; } @Override public boolean isFactValid(UnconditionalValueDerefSet fact) { return !fact.isTop() && !fact.isBottom(); } private static boolean check(InstructionHandle h, int[] opcodes) { for (int opcode : opcodes) { if (h == null) { return false; } short opcode2 = h.getInstruction().getOpcode(); if (opcode == Constants.LDC) { switch (opcode2) { case Constants.LDC: case Constants.ALOAD: case Constants.ALOAD_0: case Constants.ALOAD_1: case Constants.ALOAD_2: case Constants.ALOAD_3: break; default: return false; } } else if (opcode2 != opcode) { return false; } h = h.getNext(); } return true; } public static boolean isNullCheck(InstructionHandle h, ConstantPoolGen cpg) { if (!(h.getInstruction() instanceof IFNONNULL)) { return false; } h = h.getNext(); final Instruction newInstruction = h.getInstruction(); if (!(newInstruction instanceof NEW)) { return false; } final ObjectType loadClassType = ((NEW) newInstruction).getLoadClassType(cpg); if (!"java.lang.NullPointerException".equals(loadClassType.getClassName())) { return false; } h = h.getNext(); return check(h, NULLCHECK1) || check(h, NULLCHECK2); } private void handleNullCheck( Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { if (reportPotentialDereference(location, invDataflow.getFactAtLocation(location))) { ValueNumber vn = vnaFrame.getTopValue(); fact.addDeref(vn, location); } } public static boolean reportPotentialDereference(Location location, IsNullValueFrame invFrame) throws DataflowAnalysisException { if (!invFrame.isValid()) { return false; } IsNullValue value = invFrame.getTopValue(); if (value.isDefinitelyNotNull()) { return false; } if (value.isDefinitelyNull()) { return false; } return true; } @Override public void transferInstruction( InstructionHandle handle, BasicBlock basicBlock, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { Instruction instruction = handle.getInstruction(); if (fact.isTop()) { return; } Location location = new Location(handle, basicBlock); // If this is a call to an assertion method, // change the dataflow value to be TOP. // We don't want to report future derefs that would // be guaranteed only if the assertion methods // returns normally. // TODO: at some point, evaluate whether we should revisit this if (isAssertion(handle) // || handle.getInstruction() instanceof ATHROW ) { if (DEBUG) { System.out.println("MAKING BOTTOM0 AT: " + location); } fact.clear(); return; } // Get value number frame ValueNumberFrame vnaFrame = vnaDataflow.getFactAtLocation(location); if (!vnaFrame.isValid()) { if (DEBUG) { System.out.println("MAKING TOP1 AT: " + location); } // Probably dead code. // Assume this location can't be reached. makeFactTop(fact); return; } if (isNullCheck(handle, methodGen.getConstantPool())) { handleNullCheck(location, vnaFrame, fact); } // Check for calls to a method that unconditionally dereferences // a parameter. Mark any such arguments as derefs. if (CHECK_CALLS && instruction instanceof InvokeInstruction) { checkUnconditionalDerefDatabase(location, vnaFrame, fact); } // If this is a method call instruction, // check to see if any of the parameters are @NonNull, // and treat them as dereferences. if (CHECK_ANNOTATIONS && instruction instanceof InvokeInstruction) { checkNonNullParams(location, vnaFrame, fact); } if (CHECK_ANNOTATIONS && instruction instanceof ARETURN) { XMethod thisMethod = XFactory.createXMethod(methodGen); checkNonNullReturnValue(thisMethod, location, vnaFrame, fact); } if (CHECK_ANNOTATIONS && (instruction instanceof PUTFIELD || instruction instanceof PUTSTATIC)) { checkNonNullPutField(location, vnaFrame, fact); } // Check to see if an instance value is dereferenced here checkInstance(location, vnaFrame, fact); /* if (false) { fact.cleanDerefSet(location, vnaFrame); }*/ if (DEBUG && fact.isTop()) { System.out.println("MAKING TOP2 At: " + location); } } /** * Check method call at given location to see if it unconditionally dereferences a parameter. Mark * any such arguments as derefs. * * @param location the Location of the method call * @param vnaFrame ValueNumberFrame at the Location * @param fact the dataflow value to modify * @throws DataflowAnalysisException */ private void checkUnconditionalDerefDatabase( Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { ConstantPoolGen constantPool = methodGen.getConstantPool(); for (ValueNumber vn : checkUnconditionalDerefDatabase( location, vnaFrame, constantPool, invDataflow.getFactAtLocation(location), typeDataflow)) { fact.addDeref(vn, location); } } public static Set<ValueNumber> checkUnconditionalDerefDatabase( Location location, ValueNumberFrame vnaFrame, ConstantPoolGen constantPool, @CheckForNull IsNullValueFrame invFrame, TypeDataflow typeDataflow) throws DataflowAnalysisException { if (invFrame != null && !invFrame.isValid()) { return Collections.emptySet(); } InvokeInstruction inv = (InvokeInstruction) location.getHandle().getInstruction(); SignatureParser sigParser = new SignatureParser(inv.getSignature(constantPool)); int numParams = sigParser.getNumParameters(); if (numParams == 0 || !sigParser.hasReferenceParameters()) { return Collections.emptySet(); } ParameterNullnessPropertyDatabase database = AnalysisContext.currentAnalysisContext().getUnconditionalDerefParamDatabase(); if (database == null) { if (DEBUG_CHECK_CALLS) { System.out.println("no database!"); } return Collections.emptySet(); } TypeFrame typeFrame = typeDataflow.getFactAtLocation(location); if (!typeFrame.isValid()) { if (DEBUG_CHECK_CALLS) { System.out.println("invalid type frame!"); } return Collections.emptySet(); } try { Set<XMethod> targetSet = Hierarchy2.resolveMethodCallTargets(inv, typeFrame, constantPool); if (targetSet.isEmpty()) { return Collections.emptySet(); } if (DEBUG_CHECK_CALLS) { System.out.println("target set size: " + targetSet.size()); } // Compute the intersection of all properties ParameterProperty derefParamSet = null; for (XMethod target : targetSet) { if (target.isStub()) { continue; } if (DEBUG_CHECK_CALLS) { System.out.print("Checking: " + target + ": "); } ParameterProperty targetDerefParamSet = database.getProperty(target.getMethodDescriptor()); if (targetDerefParamSet == null) { // Hmm...no information for this target. // assume it doesn't dereference anything if (DEBUG_CHECK_CALLS) { System.out.println("==> no information, assume no guaranteed dereferences"); } return Collections.emptySet(); } if (DEBUG_CHECK_CALLS) { System.out.println("==> " + targetDerefParamSet); } if (derefParamSet == null) { derefParamSet = new ParameterProperty(); derefParamSet.copyFrom(targetDerefParamSet); } else { derefParamSet.intersectWith(targetDerefParamSet); } } if (derefParamSet == null || derefParamSet.isEmpty()) { if (DEBUG) { System.out.println("** Nothing"); } return Collections.emptySet(); } if (DEBUG_CHECK_CALLS) { System.out.println( "** Summary of call @ " + location.getHandle().getPosition() + ": " + derefParamSet); } HashSet<ValueNumber> requiredToBeNonnull = new HashSet<ValueNumber>(); for (int i = 0; i < numParams; i++) { if (!derefParamSet.hasProperty(i)) { continue; } int argSlot = vnaFrame.getStackLocation(sigParser.getSlotsFromTopOfStackForParameter(i)); if (invFrame != null && !reportDereference(invFrame, argSlot)) { continue; } if (DEBUG_CHECK_CALLS) { System.out.println( " dereference @ " + location.getHandle().getPosition() + " of parameter " + i); } requiredToBeNonnull.add(vnaFrame.getValue(argSlot)); } return requiredToBeNonnull; } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); } return Collections.emptySet(); } public static final boolean VERBOSE_NULLARG_DEBUG = SystemProperties.getBoolean("fnd.debug.nullarg.verbose"); /** * If this is a method call instruction, check to see if any of the parameters are @NonNull, and * treat them as dereferences. * * @param location the Location of the instruction * @param vnaFrame the ValueNumberFrame at the Location of the instruction * @param fact the dataflow value to modify * @throws DataflowAnalysisException */ private void checkNonNullReturnValue( XMethod thisMethod, Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { INullnessAnnotationDatabase database = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase(); if (database.getResolvedAnnotation(thisMethod, true) != NullnessAnnotation.NONNULL) { return; } if (reportPotentialDereference(location, invDataflow.getFactAtLocation(location))) { ValueNumber vn = vnaFrame.getTopValue(); fact.addDeref(vn, location); } } /** * If this is a putfield or putstatic instruction, check to see if the field is @NonNull, and * treat it as dereferences. * * @param location the Location of the instruction * @param vnaFrame the ValueNumberFrame at the Location of the instruction * @param fact the dataflow value to modify * @throws DataflowAnalysisException */ private void checkNonNullPutField( Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { INullnessAnnotationDatabase database = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase(); FieldInstruction fieldIns = (FieldInstruction) location.getHandle().getInstruction(); XField field = XFactory.createXField(fieldIns, methodGen.getConstantPool()); char firstChar = field.getSignature().charAt(0); if (firstChar != 'L' && firstChar != '[') { return; } NullnessAnnotation resolvedAnnotation = database.getResolvedAnnotation(field, true); if (resolvedAnnotation == NullnessAnnotation.NONNULL) { IsNullValueFrame invFrame = invDataflow.getFactAtLocation(location); if (!invFrame.isValid()) { return; } IsNullValue value = invFrame.getTopValue(); if (reportDereference(value)) { ValueNumber vn = vnaFrame.getTopValue(); fact.addDeref(vn, location); } } } /** * If this is a method call instruction, check to see if any of the parameters are @NonNull, and * treat them as dereferences. * * @param location the Location of the instruction * @param vnaFrame the ValueNumberFrame at the Location of the instruction * @param fact the dataflow value to modify * @throws DataflowAnalysisException */ private void checkNonNullParams( Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { ConstantPoolGen constantPool = methodGen.getConstantPool(); Set<ValueNumber> nonNullParams = checkNonNullParams( location, vnaFrame, constantPool, method, invDataflow.getFactAtLocation(location)); for (ValueNumber vn : nonNullParams) { fact.addDeref(vn, location); } } public static Set<ValueNumber> checkAllNonNullParams( Location location, ValueNumberFrame vnaFrame, ConstantPoolGen constantPool, @CheckForNull Method method, @CheckForNull IsNullValueDataflow invDataflow, TypeDataflow typeDataflow) throws DataflowAnalysisException { IsNullValueFrame invFrame = null; if (invDataflow != null) { invFrame = invDataflow.getFactAtLocation(location); } Set<ValueNumber> result1 = checkNonNullParams(location, vnaFrame, constantPool, method, invFrame); Set<ValueNumber> result2 = checkUnconditionalDerefDatabase(location, vnaFrame, constantPool, invFrame, typeDataflow); if (result1.isEmpty()) { return result2; } if (result2.isEmpty()) { return result1; } result1.addAll(result2); return result1; } public static Set<ValueNumber> checkNonNullParams( Location location, ValueNumberFrame vnaFrame, ConstantPoolGen constantPool, @CheckForNull Method method, @CheckForNull IsNullValueFrame invFrame) throws DataflowAnalysisException { if (invFrame != null && !invFrame.isValid()) { return Collections.emptySet(); } INullnessAnnotationDatabase database = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase(); InvokeInstruction inv = (InvokeInstruction) location.getHandle().getInstruction(); XMethod called = XFactory.createXMethod(inv, constantPool); SignatureParser sigParser = new SignatureParser(called.getSignature()); int numParams = sigParser.getNumParameters(); Set<ValueNumber> result = new HashSet<ValueNumber>(); Iterator<String> parameterIterator = sigParser.parameterSignatureIterator(); for (int i = 0; i < numParams; i++) { String parameterSignature = parameterIterator.next(); char firstChar = parameterSignature.charAt(0); if (firstChar != 'L' && firstChar != '[') { continue; } int offset = sigParser.getSlotsFromTopOfStackForParameter(i); if (invFrame != null) { int slot = invFrame.getStackLocation(offset); if (!reportDereference(invFrame, slot)) { continue; } } if (database.parameterMustBeNonNull(called, i)) { int catchSizeNPE = Util.getSizeOfSurroundingTryBlock( method, "java/lang/NullPointerException", location.getHandle().getPosition()); int catchSizeNFE = Util.getSizeOfSurroundingTryBlock( method, "java/lang/NumberFormatException", location.getHandle().getPosition()); if (catchSizeNPE == Integer.MAX_VALUE && (!"java.lang.Integer".equals(called.getClassName()) || catchSizeNFE == Integer.MAX_VALUE)) { // Get the corresponding value number ValueNumber vn = vnaFrame.getArgument(inv, constantPool, i, sigParser); result.add(vn); } } } return result; } /** * Check to see if the instruction has a null check associated with it, and if so, add a * dereference. * * @param location the Location of the instruction * @param vnaFrame ValueNumberFrame at the Location of the instruction * @param fact the dataflow value to modify * @throws DataflowAnalysisException */ private void checkInstance( Location location, ValueNumberFrame vnaFrame, UnconditionalValueDerefSet fact) throws DataflowAnalysisException { // See if this instruction has a null check. // If it does, the fall through predecessor will be // identify itself as the null check. if (!location.isFirstInstructionInBasicBlock()) { return; } if (invDataflow == null) { return; } BasicBlock fallThroughPredecessor = cfg.getPredecessorWithEdgeType(location.getBasicBlock(), EdgeTypes.FALL_THROUGH_EDGE); if (fallThroughPredecessor == null || !fallThroughPredecessor.isNullCheck()) { return; } // Get the null-checked value ValueNumber vn = vnaFrame.getInstance(location.getHandle().getInstruction(), methodGen.getConstantPool()); // Ignore dereferences of this if (!methodGen.isStatic()) { ValueNumber v = vnaFrame.getValue(0); if (v.equals(vn)) { return; } } if (vn.hasFlag(ValueNumber.CONSTANT_CLASS_OBJECT)) { return; } IsNullValueFrame startFact = null; startFact = invDataflow.getStartFact(fallThroughPredecessor); if (!startFact.isValid()) { return; } int slot = startFact.getInstanceSlot( location.getHandle().getInstruction(), methodGen.getConstantPool()); if (!reportDereference(startFact, slot)) { return; } if (DEBUG) { System.out.println("FOUND GUARANTEED DEREFERENCE"); System.out.println("Load: " + vnaFrame.getLoad(vn)); System.out.println("Pred: " + fallThroughPredecessor); System.out.println("startFact: " + startFact); System.out.println("Location: " + location); System.out.println("Value number frame: " + vnaFrame); System.out.println("Dereferenced valueNumber: " + vn); System.out.println("invDataflow: " + startFact); System.out.println("IGNORE_DEREF_OF_NCP: " + IGNORE_DEREF_OF_NCP); } // Mark the value number as being dereferenced at this location fact.addDeref(vn, location); } private static boolean reportDereference(IsNullValueFrame invFrameAtNullCheck, int instance) { return reportDereference(invFrameAtNullCheck.getValue(instance)); } private static boolean reportDereference(IsNullValue value) { if (value.isDefinitelyNotNull()) { return false; } if (value.isDefinitelyNull()) { return false; } if (IGNORE_DEREF_OF_NCP && value.isNullOnComplicatedPath()) { return false; } return true; } /** * Return whether or not given instruction is an assertion. * * @param handle the instruction * @return true if instruction is an assertion, false otherwise */ private boolean isAssertion(InstructionHandle handle) { return assertionMethods.isAssertionHandle(handle, methodGen.getConstantPool()); } @Override public void copy(UnconditionalValueDerefSet source, UnconditionalValueDerefSet dest) { dest.makeSameAs(source); } @Override public UnconditionalValueDerefSet createFact() { return new UnconditionalValueDerefSet(vnaDataflow.getAnalysis().getNumValuesAllocated()); } @Override public void initEntryFact(UnconditionalValueDerefSet result) throws DataflowAnalysisException { result.clear(); } // /* (non-Javadoc) // * @see // edu.umd.cs.findbugs.ba.DataflowAnalysis#initResultFact(java.lang.Object) // */ // public void initResultFact(UnconditionalValueDerefSet result) { // result.setIsTop(); // } @Override public void makeFactTop(UnconditionalValueDerefSet fact) { fact.setIsTop(); } @Override public boolean isTop(UnconditionalValueDerefSet fact) { return fact.isTop(); } @Override public void meetInto( UnconditionalValueDerefSet fact, Edge edge, UnconditionalValueDerefSet result) throws DataflowAnalysisException { meetInto(fact, edge, result, false); } public void meetInto( UnconditionalValueDerefSet fact, Edge edge, UnconditionalValueDerefSet result, boolean onlyEdge) { if (isExceptionEdge(edge) && !onlyEdge) { if (DEBUG) { System.out.println("Skipping exception edge"); } return; } ValueNumber knownNonnullOnBranch = null; // Edge transfer function if (isFactValid(fact)) { fact = propagateDerefSetsToMergeInputValues(fact, edge); if (invDataflow != null) { knownNonnullOnBranch = findValueKnownNonnullOnBranch(fact, edge); if (knownNonnullOnBranch != null) { fact = duplicateFact(fact); fact.clearDerefSet(knownNonnullOnBranch); } } } boolean isBackEdge = edge.isBackwardInBytecode(); Set<Integer> loopExitBranches = ClassContext.getLoopExitBranches(method, methodGen); assert loopExitBranches != null; boolean sourceIsTopOfLoop = edge.sourceIsTopOfLoop(loopExitBranches); if (sourceIsTopOfLoop && edge.getType() == EdgeTypes.FALL_THROUGH_EDGE) { isBackEdge = true; } /* if (false && (edge.getType() == EdgeTypes.IFCMP_EDGE || sourceIsTopOfLoop)) { System.out.println("Meet into " + edge); System.out.println(" foo2: " + sourceIsTopOfLoop); System.out.println(" getType: " + edge.getType()); System.out.println(" Backedge according to bytecode: " + isBackEdge); System.out.println(" Fact hashCode: " + System.identityHashCode(result)); System.out.println(" Initial fact: " + result); System.out.println(" Edge fact: " + fact); } */ if (result.isTop() || fact.isBottom()) { // Make result identical to other fact copy(fact, result); if (ASSUME_NONZERO_TRIP_LOOPS && isBackEdge && !fact.isTop()) { result.resultsFromBackEdge = true; } } else if (ASSUME_NONZERO_TRIP_LOOPS && isBackEdge && !fact.isTop()) { result.unionWith(fact, vnaDataflow.getAnalysis().getFactory()); result.resultsFromBackEdge = true; if (DEBUG) { System.out.println( "\n Forcing union of " + System.identityHashCode(result) + " due to backedge info"); System.out.println(" result: " + result); } } else if (result.isBottom() || fact.isTop()) { // No change in result fact } else { // Dataflow merge // (intersection of unconditional deref values) if (ASSUME_NONZERO_TRIP_LOOPS && result.resultsFromBackEdge) { result.backEdgeUpdateCount++; if (result.backEdgeUpdateCount < 10) { if (DEBUG) { System.out.println( "\n Union update of " + System.identityHashCode(result) + " due to backedge info"); } result.unionWith(fact, vnaDataflow.getAnalysis().getFactory()); return; } } result.mergeWith(fact, knownNonnullOnBranch, vnaDataflow.getAnalysis().getFactory()); if (DEBUG) { System.out.println(" updated: " + System.identityHashCode(result)); System.out.println(" result: " + result); } } if (DEBUG && isBackEdge && edge.getType() == EdgeTypes.IFCMP_EDGE) { System.out.println(" result: " + result); } } /** * Find out if any VNs in the source block contribute to unconditionally dereferenced VNs in the * target block. If so, the VN in the source block is also unconditionally dereferenced, and we * must propagate the target VN's dereferences. * * @param fact a dataflow value * @param edge edge to check for merge input values * @return possibly-modified dataflow value */ private UnconditionalValueDerefSet propagateDerefSetsToMergeInputValues( UnconditionalValueDerefSet fact, Edge edge) { ValueNumberFrame blockValueNumberFrame = vnaDataflow.getResultFact(edge.getSource()); ValueNumberFrame targetValueNumberFrame = vnaDataflow.getStartFact(edge.getTarget()); UnconditionalValueDerefSet originalFact = fact; fact = duplicateFact(fact); if (blockValueNumberFrame.isValid() && targetValueNumberFrame.isValid()) { int slots = 0; if (targetValueNumberFrame.getNumSlots() == blockValueNumberFrame.getNumSlots()) { slots = targetValueNumberFrame.getNumSlots(); } else if (targetValueNumberFrame.getNumLocals() == blockValueNumberFrame.getNumLocals()) { slots = targetValueNumberFrame.getNumLocals(); } if (slots > 0) { if (DEBUG) { System.out.println("** Valid VNA frames for " + edge); System.out.println("** Block : " + blockValueNumberFrame); System.out.println("** Target: " + targetValueNumberFrame); } for (int i = 0; i < slots; i++) { ValueNumber blockVN = blockValueNumberFrame.getValue(i); ValueNumber targetVN = targetValueNumberFrame.getValue(i); if (blockVN.equals(targetVN)) { continue; } fact.clearDerefSet(blockVN); if (originalFact.isUnconditionallyDereferenced(targetVN)) { fact.setDerefSet(blockVN, originalFact.getUnconditionalDerefLocationSet(targetVN)); } } // for all slots for (ValueNumber blockVN : blockValueNumberFrame.valueNumbersForLoads()) { AvailableLoad load = blockValueNumberFrame.getLoad(blockVN); if (load == null) { continue; } ValueNumber[] targetVNs = targetValueNumberFrame.getAvailableLoad(load); if (targetVNs != null) { for (ValueNumber targetVN : targetVNs) { if (targetVN.hasFlag(ValueNumber.PHI_NODE) && fact.isUnconditionallyDereferenced(targetVN) && !fact.isUnconditionallyDereferenced(blockVN)) { // Block VN is also dereferenced // unconditionally. AvailableLoad targetLoad = targetValueNumberFrame.getLoad(targetVN); if (!load.equals(targetLoad)) { continue; } if (DEBUG) { System.out.println( "** Copy vn derefs for " + load + " from " + targetVN + " --> " + blockVN); System.out.println( "** block phi for " + System.identityHashCode(blockValueNumberFrame) + " is " + blockValueNumberFrame.phiNodeForLoads); System.out.println( "** target phi for " + System.identityHashCode(targetValueNumberFrame) + " is " + targetValueNumberFrame.phiNodeForLoads); } fact.setDerefSet(blockVN, fact.getUnconditionalDerefLocationSet(targetVN)); } } } } } } if (DEBUG) { System.out.println("Target VNF: " + targetValueNumberFrame); System.out.println("Block VNF: " + blockValueNumberFrame); System.out.println("fact: " + fact); } fact.cleanDerefSet(null, blockValueNumberFrame); return fact; } /** * Return a duplicate of given dataflow fact. * * @param fact a dataflow fact * @return a duplicate of the input dataflow fact */ private UnconditionalValueDerefSet duplicateFact(UnconditionalValueDerefSet fact) { UnconditionalValueDerefSet copyOfFact = createFact(); copy(fact, copyOfFact); fact = copyOfFact; return fact; } /** * Clear deref sets of values if this edge is the non-null branch of an if comparison. * * @param fact a datflow fact * @param edge edge to check * @return possibly-modified dataflow fact */ private @CheckForNull ValueNumber findValueKnownNonnullOnBranch( UnconditionalValueDerefSet fact, Edge edge) { IsNullValueFrame invFrame = invDataflow.getResultFact(edge.getSource()); if (!invFrame.isValid()) { return null; } IsNullConditionDecision decision = invFrame.getDecision(); if (decision == null) { return null; } IsNullValue inv = decision.getDecision(edge.getType()); if (inv == null || !inv.isDefinitelyNotNull()) { return null; } ValueNumber value = decision.getValue(); if (DEBUG) { System.out.println("Value number " + value + " is known nonnull on " + edge); } return value; } /** * Determine whether dataflow should be propagated on given edge. * * @param edge the edge * @return true if dataflow should be propagated on the edge, false otherwise */ private boolean isExceptionEdge(Edge edge) { boolean isExceptionEdge = edge.isExceptionEdge(); if (isExceptionEdge) { if (DEBUG) { System.out.println("NOT Ignoring " + edge); } return true; // false } if (edge.getType() != EdgeTypes.FALL_THROUGH_EDGE) { return false; } InstructionHandle h = edge.getSource().getLastInstruction(); if (h != null && h.getInstruction() instanceof IFNONNULL && isNullCheck(h, methodGen.getConstantPool())) { return true; } return false; } @Override public boolean same(UnconditionalValueDerefSet fact1, UnconditionalValueDerefSet fact2) { return fact1.resultsFromBackEdge || fact1.isSameAs(fact2); } @Override public void startIteration() { // System.out.println("analysis iteration in " + // methodGen.getClassName() + " on " + methodGen.toString()); } @Override public int getLastUpdateTimestamp(UnconditionalValueDerefSet fact) { return fact.getLastUpdateTimestamp(); } @Override public void setLastUpdateTimestamp(UnconditionalValueDerefSet fact, int lastUpdate) { fact.setLastUpdateTimestamp(lastUpdate); } // public static void main(String[] args) throws Exception { // if (args.length != 1) { // System.err.println("Usage: " + // UnconditionalValueDerefAnalysis.class.getName() + " <classfile>"); // System.exit(1); // } // // DataflowTestDriver<UnconditionalValueDerefSet, // UnconditionalValueDerefAnalysis> driver = // new DataflowTestDriver<UnconditionalValueDerefSet, // UnconditionalValueDerefAnalysis>() { // /* (non-Javadoc) // * @see // edu.umd.cs.findbugs.ba.DataflowTestDriver#createDataflow(edu.umd.cs.findbugs.ba.ClassContext, // org.apache.bcel.classfile.Method) // */ // @Override // public Dataflow<UnconditionalValueDerefSet, // UnconditionalValueDerefAnalysis> createDataflow(ClassContext // classContext, Method method) throws CFGBuilderException, // DataflowAnalysisException { // return classContext.getUnconditionalValueDerefDataflow(method); // } // }; // if (SystemProperties.getBoolean("forwardcfg")) { // driver.overrideIsForwards(); // } // driver.execute(args[0]); // } }
public class SerializableIdiom extends OpcodeStackDetector { private static final boolean DEBUG = SystemProperties.getBoolean("se.debug"); static final boolean reportTransientFieldOfNonSerializableClass = SystemProperties.getBoolean("reportTransientFieldOfNonSerializableClass"); boolean sawSerialVersionUID; boolean isSerializable, implementsSerializableDirectly; boolean isExternalizable; boolean isGUIClass; boolean isEjbImplClass; boolean foundSynthetic; boolean seenTransientField; boolean foundSynchronizedMethods; boolean writeObjectIsSynchronized; private BugReporter bugReporter; boolean isAbstract; private List<BugInstance> fieldWarningList = new LinkedList<BugInstance>(); private HashMap<String, XField> fieldsThatMightBeAProblem = new HashMap<String, XField>(); private HashMap<XField, Integer> transientFieldsUpdates = new HashMap<XField, Integer>(); private HashSet<XField> transientFieldsSetInConstructor = new HashSet<XField>(); private HashSet<XField> transientFieldsSetToDefaultValueInConstructor = new HashSet<XField>(); private boolean sawReadExternal; private boolean sawWriteExternal; private boolean sawReadObject; private boolean sawReadResolve; private boolean sawWriteObject; private boolean superClassImplementsSerializable; private boolean superClassHasReadObject; private boolean hasPublicVoidConstructor; private boolean superClassHasVoidConstructor; private boolean directlyImplementsExternalizable; // private JavaClass serializable; // private JavaClass collection; // private JavaClass map; // private boolean isRemote; public SerializableIdiom(BugReporter bugReporter) { this.bugReporter = bugReporter; } @Override public void visitClassContext(ClassContext classContext) { classContext.getJavaClass().accept(this); flush(); } private void flush() { if (!isAbstract && !((sawReadExternal && sawWriteExternal) || (sawReadObject && sawWriteObject))) { for (BugInstance aFieldWarningList : fieldWarningList) bugReporter.reportBug(aFieldWarningList); } fieldWarningList.clear(); } static final Pattern anonymousInnerClassNamePattern = Pattern.compile(".+\\$\\d+"); boolean isAnonymousInnerClass; boolean innerClassHasOuterInstance; private boolean isEnum; @Override public void visit(JavaClass obj) { String superClassname = obj.getSuperclassName(); // System.out.println("superclass of " + getClassName() + " is " + superClassname); isEnum = superClassname.equals("java.lang.Enum"); if (isEnum) return; int flags = obj.getAccessFlags(); isAbstract = (flags & ACC_ABSTRACT) != 0 || (flags & ACC_INTERFACE) != 0; isAnonymousInnerClass = anonymousInnerClassNamePattern.matcher(getClassName()).matches(); innerClassHasOuterInstance = false; for (Field f : obj.getFields()) { if (f.getName().equals("this$0")) { innerClassHasOuterInstance = true; break; } } sawSerialVersionUID = false; isSerializable = implementsSerializableDirectly = false; isExternalizable = false; directlyImplementsExternalizable = false; isGUIClass = false; isEjbImplClass = false; seenTransientField = false; // boolean isEnum = obj.getSuperclassName().equals("java.lang.Enum"); fieldsThatMightBeAProblem.clear(); transientFieldsUpdates.clear(); transientFieldsSetInConstructor.clear(); transientFieldsSetToDefaultValueInConstructor.clear(); // isRemote = false; // Does this class directly implement Serializable? String[] interface_names = obj.getInterfaceNames(); for (String interface_name : interface_names) { if (interface_name.equals("java.io.Externalizable")) { directlyImplementsExternalizable = true; isExternalizable = true; if (DEBUG) { System.out.println("Directly implements Externalizable: " + getClassName()); } } else if (interface_name.equals("java.io.Serializable")) { implementsSerializableDirectly = true; isSerializable = true; if (DEBUG) { System.out.println("Directly implements Serializable: " + getClassName()); } break; } } // Does this class indirectly implement Serializable? if (!isSerializable) { if (Subtypes2.instanceOf(obj, "java.io.Externalizable")) { isExternalizable = true; if (DEBUG) { System.out.println("Indirectly implements Externalizable: " + getClassName()); } } if (Subtypes2.instanceOf(obj, "java.io.Serializable")) { isSerializable = true; if (DEBUG) { System.out.println("Indirectly implements Serializable: " + getClassName()); } } } hasPublicVoidConstructor = false; superClassHasVoidConstructor = true; superClassHasReadObject = false; superClassImplementsSerializable = isSerializable && !implementsSerializableDirectly; ClassDescriptor superclassDescriptor = getXClass().getSuperclassDescriptor(); if (superclassDescriptor != null) try { XClass superXClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, superclassDescriptor); if (superXClass != null) { superClassImplementsSerializable = AnalysisContext.currentAnalysisContext() .getSubtypes2() .isSubtype( superXClass.getClassDescriptor(), DescriptorFactory.createClassDescriptor(java.io.Serializable.class)); superClassHasVoidConstructor = false; for (XMethod m : superXClass.getXMethods()) { if (m.getName().equals("<init>") && m.getSignature().equals("()V") && !m.isPrivate()) { superClassHasVoidConstructor = true; } if (m.getName().equals("readObject") && m.getSignature().equals("(Ljava/io/ObjectInputStream;)V") && m.isPrivate()) superClassHasReadObject = true; } } } catch (ClassNotFoundException e) { bugReporter.reportMissingClass(e); } catch (CheckedAnalysisException e) { bugReporter.logError("huh", e); } // Is this a GUI or other class that is rarely serialized? isGUIClass = false; isEjbImplClass = false; if (true || !directlyImplementsExternalizable && !implementsSerializableDirectly) { isEjbImplClass = Subtypes2.instanceOf(obj, "javax.ejb.SessionBean"); isGUIClass = (Subtypes2.instanceOf(obj, "java.lang.Throwable") || Subtypes2.instanceOf(obj, "java.awt.Component") || Subtypes2.instanceOf(obj, "java.awt.Component$AccessibleAWTComponent") || Subtypes2.instanceOf(obj, "java.awt.event.ActionListener") || Subtypes2.instanceOf(obj, "java.util.EventListener")); if (!isGUIClass) { JavaClass o = obj; while (o != null) { if (o.getClassName().startsWith("java.awt") || o.getClassName().startsWith("javax.swing")) { isGUIClass = true; break; } try { o = o.getSuperClass(); } catch (ClassNotFoundException e) { break; } } } } foundSynthetic = false; foundSynchronizedMethods = false; writeObjectIsSynchronized = false; sawReadExternal = sawWriteExternal = sawReadObject = sawReadResolve = sawWriteObject = false; if (isSerializable) { for (Method m : obj.getMethods()) { if (m.getName().equals("readObject") && m.getSignature().equals("(Ljava/io/ObjectInputStream;)V")) sawReadObject = true; else if (m.getName().equals("readResolve") && m.getSignature().startsWith("()")) sawReadResolve = true; else if (m.getName().equals("readObjectNoData") && m.getSignature().equals("()V")) sawReadObject = true; else if (m.getName().equals("writeObject") && m.getSignature().equals("(Ljava/io/ObjectOutputStream;)V")) sawWriteObject = true; } for (Field f : obj.getFields()) { if (f.isTransient()) seenTransientField = true; } } } private boolean strongEvidenceForIntendedSerialization() { return implementsSerializableDirectly || sawReadObject || sawReadResolve || sawWriteObject || seenTransientField; } @Override public void visitAfter(JavaClass obj) { if (isEnum) return; if (DEBUG) { System.out.println(getDottedClassName()); System.out.println(" hasPublicVoidConstructor: " + hasPublicVoidConstructor); System.out.println(" superClassHasVoidConstructor: " + superClassHasVoidConstructor); System.out.println(" isExternalizable: " + isExternalizable); System.out.println(" isSerializable: " + isSerializable); System.out.println(" isAbstract: " + isAbstract); System.out.println(" superClassImplementsSerializable: " + superClassImplementsSerializable); System.out.println(" isGUIClass: " + isGUIClass); System.out.println(" isEjbImplClass: " + isEjbImplClass); } if (isSerializable && !sawReadObject && !sawReadResolve && seenTransientField && !superClassHasReadObject) { for (Map.Entry<XField, Integer> e : transientFieldsUpdates.entrySet()) { XField fieldX = e.getKey(); int priority = NORMAL_PRIORITY; if (transientFieldsSetInConstructor.contains(e.getKey())) priority--; if (isGUIClass) priority++; if (isEjbImplClass) priority++; if (e.getValue() < 3) priority++; if (transientFieldsSetToDefaultValueInConstructor.contains(e.getKey())) priority++; if (obj.isAbstract()) { priority++; if (priority < Priorities.LOW_PRIORITY) priority = Priorities.LOW_PRIORITY; } try { double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(fieldX.getSignature()); if (isSerializable < 0.6) priority++; } catch (ClassNotFoundException e1) { // ignore it } bugReporter.reportBug( new BugInstance(this, "SE_TRANSIENT_FIELD_NOT_RESTORED", priority) .addClass(getThisClass()) .addField(fieldX)); } } if (isSerializable && !isExternalizable && !superClassHasVoidConstructor && !superClassImplementsSerializable) { int priority = implementsSerializableDirectly || seenTransientField ? HIGH_PRIORITY : (sawSerialVersionUID ? NORMAL_PRIORITY : LOW_PRIORITY); if (isGUIClass) priority++; if (isEjbImplClass) priority++; bugReporter.reportBug( new BugInstance(this, "SE_NO_SUITABLE_CONSTRUCTOR", priority) .addClass(getThisClass().getClassName())); } // Downgrade class-level warnings if it's a GUI or EJB-implementation class. int priority = (isGUIClass || isEjbImplClass) ? LOW_PRIORITY : NORMAL_PRIORITY; if (obj.getClassName().endsWith("_Stub")) priority++; if (isExternalizable && !hasPublicVoidConstructor && !isAbstract) bugReporter.reportBug( new BugInstance( this, "SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION", directlyImplementsExternalizable ? HIGH_PRIORITY : NORMAL_PRIORITY) .addClass(getThisClass().getClassName())); if (!foundSynthetic) priority++; if (seenTransientField) priority--; if (!isAnonymousInnerClass && !isExternalizable && !isGUIClass && !obj.isAbstract() && isSerializable && !isAbstract && !sawSerialVersionUID && !isEjbImplClass) bugReporter.reportBug( new BugInstance(this, "SE_NO_SERIALVERSIONID", priority).addClass(this)); if (writeObjectIsSynchronized && !foundSynchronizedMethods) bugReporter.reportBug( new BugInstance(this, "WS_WRITEOBJECT_SYNC", LOW_PRIORITY).addClass(this)); } @Override public void visit(Method obj) { int accessFlags = obj.getAccessFlags(); boolean isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0; if (getMethodName().equals("<init>") && getMethodSig().equals("()V") && (accessFlags & ACC_PUBLIC) != 0) hasPublicVoidConstructor = true; if (!getMethodName().equals("<init>") && isSynthetic(obj)) foundSynthetic = true; // System.out.println(methodName + isSynchronized); if (getMethodName().equals("readExternal") && getMethodSig().equals("(Ljava/io/ObjectInput;)V")) { sawReadExternal = true; if (DEBUG && !obj.isPrivate()) System.out.println("Non-private readExternal method in: " + getDottedClassName()); } else if (getMethodName().equals("writeExternal") && getMethodSig().equals("(Ljava/io/Objectoutput;)V")) { sawWriteExternal = true; if (DEBUG && !obj.isPrivate()) System.out.println("Non-private writeExternal method in: " + getDottedClassName()); } else if (getMethodName().equals("readResolve") && getMethodSig().startsWith("()") && isSerializable) { sawReadResolve = true; if (!getMethodSig().equals("()Ljava/lang/Object;")) bugReporter.reportBug( new BugInstance(this, "SE_READ_RESOLVE_MUST_RETURN_OBJECT", HIGH_PRIORITY) .addClassAndMethod(this)); else if (obj.isStatic()) bugReporter.reportBug( new BugInstance(this, "SE_READ_RESOLVE_IS_STATIC", HIGH_PRIORITY) .addClassAndMethod(this)); else if (obj.isPrivate()) try { Set<ClassDescriptor> subtypes = AnalysisContext.currentAnalysisContext() .getSubtypes2() .getSubtypes(getClassDescriptor()); if (subtypes.size() > 1) { BugInstance bug = new BugInstance(this, "SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", NORMAL_PRIORITY) .addClassAndMethod(this); boolean nasty = false; for (ClassDescriptor subclass : subtypes) if (!subclass.equals(getClassDescriptor())) { XClass xSub = AnalysisContext.currentXFactory().getXClass(subclass); if (xSub != null && xSub.findMethod("readResolve", "()Ljava/lang/Object;", false) == null && xSub.findMethod("writeReplace", "()Ljava/lang/Object;", false) == null) { bug.addClass(subclass).describe(ClassAnnotation.SUBCLASS_ROLE); nasty = true; } } if (nasty) bug.setPriority(HIGH_PRIORITY); else if (!getThisClass().isPublic()) bug.setPriority(LOW_PRIORITY); bugReporter.reportBug(bug); } } catch (ClassNotFoundException e) { bugReporter.reportMissingClass(e); } } else if (getMethodName().equals("readObject") && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V") && isSerializable) { sawReadObject = true; if (!obj.isPrivate()) bugReporter.reportBug( new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) .addClassAndMethod(this)); } else if (getMethodName().equals("readObjectNoData") && getMethodSig().equals("()V") && isSerializable) { if (!obj.isPrivate()) bugReporter.reportBug( new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) .addClassAndMethod(this)); } else if (getMethodName().equals("writeObject") && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V") && isSerializable) { sawWriteObject = true; if (!obj.isPrivate()) bugReporter.reportBug( new BugInstance(this, "SE_METHOD_MUST_BE_PRIVATE", HIGH_PRIORITY) .addClassAndMethod(this)); } if (isSynchronized) { if (getMethodName().equals("readObject") && getMethodSig().equals("(Ljava/io/ObjectInputStream;)V") && isSerializable) bugReporter.reportBug( new BugInstance(this, "RS_READOBJECT_SYNC", NORMAL_PRIORITY).addClass(this)); else if (getMethodName().equals("writeObject") && getMethodSig().equals("(Ljava/io/ObjectOutputStream;)V") && isSerializable) writeObjectIsSynchronized = true; else foundSynchronizedMethods = true; } super.visit(obj); } boolean isSynthetic(FieldOrMethod obj) { Attribute[] a = obj.getAttributes(); for (Attribute aA : a) if (aA instanceof Synthetic) return true; return false; } @Override public void visit(Code obj) { if (isSerializable) { super.visit(obj); } } @Override public void sawOpcode(int seen) { if (seen == PUTFIELD) { XField xField = getXFieldOperand(); if (xField != null && xField.getClassDescriptor().equals(getClassDescriptor())) { Item first = stack.getStackItem(0); boolean isPutOfDefaultValue = first.isNull(); // huh?? || first.isInitialParameter(); if (!isPutOfDefaultValue && first.getConstant() != null) { Object constant = first.getConstant(); if (constant instanceof Number && ((Number) constant).intValue() == 0 || constant.equals(Boolean.FALSE)) isPutOfDefaultValue = true; } if (isPutOfDefaultValue) { if (getMethodName().equals("<init>")) transientFieldsSetToDefaultValueInConstructor.add(xField); } else { String nameOfField = getNameConstantOperand(); if (transientFieldsUpdates.containsKey(xField)) { if (getMethodName().equals("<init>")) transientFieldsSetInConstructor.add(xField); else transientFieldsUpdates.put(xField, transientFieldsUpdates.get(xField) + 1); } else if (fieldsThatMightBeAProblem.containsKey(nameOfField)) { try { JavaClass classStored = first.getJavaClass(); if (classStored == null) { return; } double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(classStored); if (isSerializable <= 0.2) { XField f = fieldsThatMightBeAProblem.get(nameOfField); String sig = f.getSignature(); // System.out.println("Field signature: " + sig); // System.out.println("Class stored: " + // classStored.getClassName()); String genSig = "L" + classStored.getClassName().replace('.', '/') + ";"; if (!sig.equals(genSig)) { double bias = 0.0; if (!getMethodName().equals("<init>")) bias = 1.0; int priority = computePriority(isSerializable, bias); fieldWarningList.add( new BugInstance(this, "SE_BAD_FIELD_STORE", priority) .addClass(getThisClass().getClassName()) .addField(f) .addType(genSig) .describe("TYPE_FOUND") .addSourceLine(this)); } } } catch (Exception e) { // ignore it } } } } } } @Override public void visit(Field obj) { int flags = obj.getAccessFlags(); if (isEjbImplClass) { ClassDescriptor fieldType = DescriptorFactory.createClassDescriptorFromFieldSignature(obj.getSignature()); if (fieldType != null) { if (Subtypes2.instanceOf(fieldType, "javax.ejb.SessionContext") || Subtypes2.instanceOf(fieldType, "javax.transaction.UserTransaction") || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBHome") || Subtypes2.instanceOf(fieldType, "javax.ejb.EJBObject") || Subtypes2.instanceOf(fieldType, "javax.naming.Context")) { if (obj.isTransient()) bugReporter.reportBug( new BugInstance(this, "UNKNOWN", NORMAL_PRIORITY) .addClass(this) .addVisitedField(this)); return; } } } if (obj.isTransient()) { if (isSerializable && !isExternalizable) { seenTransientField = true; transientFieldsUpdates.put(getXField(), 0); } else if (reportTransientFieldOfNonSerializableClass) { bugReporter.reportBug( new BugInstance(this, "SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS", NORMAL_PRIORITY) .addClass(this) .addVisitedField(this)); } } else if (getClassName().indexOf("ObjectStreamClass") == -1 && isSerializable && !isExternalizable && getFieldSig().indexOf("L") >= 0 && !obj.isTransient() && !obj.isStatic()) { if (DEBUG) { System.out.println( "Examining non-transient field with name: " + getFieldName() + ", sig: " + getFieldSig()); } try { double isSerializable = DeepSubtypeAnalysis.isDeepSerializable(getFieldSig()); if (DEBUG) { System.out.println(" isSerializable: " + isSerializable); } if (isSerializable < 1.0) fieldsThatMightBeAProblem.put(obj.getName(), XFactory.createXField(this)); if (isSerializable < 0.9) { // Priority is LOW for GUI classes (unless explicitly marked Serializable), // HIGH if the class directly implements Serializable, // NORMAL otherwise. int priority = computePriority(isSerializable, 0); if (!strongEvidenceForIntendedSerialization()) { if (obj.getName().startsWith("this$")) priority = Math.max(priority, NORMAL_PRIORITY); else if (innerClassHasOuterInstance) { if (isAnonymousInnerClass) priority += 2; else priority += 1; } if (isGUIClass || isEjbImplClass) priority++; } else if (isGUIClass || isEjbImplClass) priority = Math.max(priority, NORMAL_PRIORITY); if (DEBUG) System.out.println( "SE_BAD_FIELD: " + getThisClass().getClassName() + " " + obj.getName() + " " + isSerializable + " " + implementsSerializableDirectly + " " + sawSerialVersionUID + " " + isGUIClass + " " + isEjbImplClass); // Report is queued until after the entire class has been seen. if (obj.getName().equals("this$0")) fieldWarningList.add( new BugInstance(this, "SE_BAD_FIELD_INNER_CLASS", priority) .addClass(getThisClass().getClassName())); else if (isSerializable < 0.9) fieldWarningList.add( new BugInstance(this, "SE_BAD_FIELD", priority) .addClass(getThisClass().getClassName()) .addField(getDottedClassName(), obj.getName(), getFieldSig(), false)); } else if (!isGUIClass && !isEjbImplClass && obj.getName().equals("this$0")) fieldWarningList.add( new BugInstance( this, "SE_INNER_CLASS", implementsSerializableDirectly ? NORMAL_PRIORITY : LOW_PRIORITY) .addClass(getThisClass().getClassName())); } catch (ClassNotFoundException e) { if (DEBUG) { System.out.println("Caught ClassNotFoundException"); } bugReporter.reportMissingClass(e); } } if (!getFieldName().startsWith("this") && isSynthetic(obj)) foundSynthetic = true; if (!getFieldName().equals("serialVersionUID")) return; int mask = ACC_STATIC | ACC_FINAL; if (!getFieldSig().equals("I") && !getFieldSig().equals("J")) return; if ((flags & mask) == mask && getFieldSig().equals("I")) { bugReporter.reportBug( new BugInstance(this, "SE_NONLONG_SERIALVERSIONID", LOW_PRIORITY) .addClass(this) .addVisitedField(this)); sawSerialVersionUID = true; return; } else if ((flags & ACC_STATIC) == 0) { bugReporter.reportBug( new BugInstance(this, "SE_NONSTATIC_SERIALVERSIONID", NORMAL_PRIORITY) .addClass(this) .addVisitedField(this)); return; } else if ((flags & ACC_FINAL) == 0) { bugReporter.reportBug( new BugInstance(this, "SE_NONFINAL_SERIALVERSIONID", NORMAL_PRIORITY) .addClass(this) .addVisitedField(this)); return; } sawSerialVersionUID = true; } private int computePriority(double isSerializable, double bias) { int priority = (int) (1.9 + isSerializable * 3 + bias); if (strongEvidenceForIntendedSerialization()) priority--; else if (sawSerialVersionUID && priority > NORMAL_PRIORITY) priority--; else priority = Math.max(priority, NORMAL_PRIORITY); return priority; } }
/** * Class to open input streams on source files. It maintains a "source path", which is like a * classpath, but for finding source files instead of class files. */ public class SourceFinder { private static final boolean DEBUG = SystemProperties.getBoolean("srcfinder.debug"); private static final int CACHE_SIZE = 50; /* * ---------------------------------------------------------------------- * Helper classes * ---------------------------------------------------------------------- */ /** * Cache of SourceFiles. We use this to avoid repeatedly having to read frequently accessed source * files. */ private static class Cache extends LinkedHashMap<String, SourceFile> { /** */ private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Map.Entry<String, SourceFile> eldest) { return size() >= CACHE_SIZE; } } /** A repository of source files. */ private interface SourceRepository { public boolean contains(String fileName); public boolean isPlatformDependent(); public SourceFileDataSource getDataSource(String fileName); } /** A directory containing source files. */ private static class DirectorySourceRepository implements SourceRepository { private String baseDir; public DirectorySourceRepository(String baseDir) { this.baseDir = baseDir; } @Override public String toString() { return "DirectorySourceRepository:" + baseDir; } public boolean contains(String fileName) { File file = new File(getFullFileName(fileName)); boolean exists = file.exists(); if (DEBUG) System.out.println("Exists " + exists + " for " + file); return exists; } public boolean isPlatformDependent() { return true; } public SourceFileDataSource getDataSource(String fileName) { return new FileSourceFileDataSource(getFullFileName(fileName)); } private String getFullFileName(String fileName) { return baseDir + File.separator + fileName; } } private static class InMemorySourceRepository implements SourceRepository { Map<String, byte[]> contents = new HashMap<String, byte[]>(); Map<String, Long> lastModified = new HashMap<String, Long>(); InMemorySourceRepository(@WillClose ZipInputStream in) throws IOException { try { while (true) { ZipEntry e = in.getNextEntry(); if (e == null) break; if (!e.isDirectory()) { String name = e.getName(); long size = e.getSize(); if (size > Integer.MAX_VALUE) throw new IOException(name + " is too big at " + size + " bytes"); ByteArrayOutputStream out; if (size <= 0) out = new ByteArrayOutputStream(); else out = new ByteArrayOutputStream((int) size); GZIPOutputStream gOut = new GZIPOutputStream(out); IO.copy(in, gOut); gOut.close(); byte data[] = out.toByteArray(); contents.put(name, data); lastModified.put(name, e.getTime()); } in.closeEntry(); } } finally { Util.closeSilently(in); } } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#contains(java * .lang.String) */ public boolean contains(String fileName) { return contents.containsKey(fileName); } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#getDataSource * (java.lang.String) */ public SourceFileDataSource getDataSource(final String fileName) { return new SourceFileDataSource() { public String getFullFileName() { return fileName; } public InputStream open() throws IOException { return new GZIPInputStream(new ByteArrayInputStream(contents.get(fileName))); } public long getLastModified() { Long when = lastModified.get(fileName); if (when == null || when < 0) return 0; return when; } }; } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.ba.SourceFinder.SourceRepository#isPlatformDependent * () */ public boolean isPlatformDependent() { return false; } } SourceRepository makeInMemorySourceRepository(final String url) { final BlockingSourceRepository r = new BlockingSourceRepository(); Util.runInDameonThread( new Runnable() { public void run() { InputStream in = null; try { URLConnection connection = new URL(url).openConnection(); in = connection.getInputStream(); if (getProject().isGuiAvaliable()) in = getProject() .getGuiCallback() .getProgressMonitorInputStream( in, connection.getContentLength(), "Downloading project source code..."); if (url.endsWith(".z0p.gz")) in = new GZIPInputStream(in); r.setBase(new InMemorySourceRepository(new ZipInputStream(in))); } catch (IOException e) { if (getProject().isGuiAvaliable()) { getProject() .getGuiCallback() .setErrorMessage("Unable to load " + url + "; " + e.getMessage()); } AnalysisContext.logError("Unable to load " + url, e); Util.closeSilently(in); } } }, "Source loading thread"); return r; } SourceRepository makeJarURLConnectionSourceRepository(final String url) throws MalformedURLException, IOException { final File file = File.createTempFile("jar_cache", null); file.deleteOnExit(); final BlockingSourceRepository r = new BlockingSourceRepository(); Util.runInDameonThread( new Runnable() { public void run() { InputStream in = null; OutputStream out = null; try { URLConnection connection = new URL(url).openConnection(); if (getProject().isGuiAvaliable()) { int size = connection.getContentLength(); in = getProject() .getGuiCallback() .getProgressMonitorInputStream( connection.getInputStream(), size, "Loading source via url"); } else { in = connection.getInputStream(); } out = new FileOutputStream(file); IO.copy(in, out); r.setBase(new ZipSourceRepository(new ZipFile(file))); } catch (IOException e) { assert true; } finally { Util.closeSilently(in); Util.closeSilently(out); } } }, "Source loading thread"); return r; } static class BlockingSourceRepository implements SourceRepository { SourceRepository base; final CountDownLatch ready = new CountDownLatch(1); public BlockingSourceRepository() {} public boolean isReady() { return ready.getCount() == 0; } public void setBase(SourceRepository base) { this.base = base; ready.countDown(); } private void await() { try { ready.await(); } catch (InterruptedException e) { throw new IllegalStateException("Unexpected interrupt", e); } } public boolean contains(String fileName) { await(); return base.contains(fileName); } public SourceFileDataSource getDataSource(String fileName) { await(); return base.getDataSource(fileName); } public boolean isPlatformDependent() { await(); return base.isPlatformDependent(); } } /** A zip or jar archive containing source files. */ static class ZipSourceRepository implements SourceRepository { ZipFile zipFile; public ZipSourceRepository(ZipFile zipFile) { this.zipFile = zipFile; } public boolean contains(String fileName) { return zipFile.getEntry(fileName) != null; } public boolean isPlatformDependent() { return false; } public SourceFileDataSource getDataSource(String fileName) { return new ZipSourceFileDataSource(zipFile, fileName); } } /* * ---------------------------------------------------------------------- * Fields * ---------------------------------------------------------------------- */ private List<SourceRepository> repositoryList; private Cache cache; private Project project; /* * ---------------------------------------------------------------------- * Public methods * ---------------------------------------------------------------------- */ public SourceFinder(Project project) { setProject(project); } /** @return Returns the project. */ public Project getProject() { return project; } /** Set the list of source directories. */ void setSourceBaseList(Iterable<String> sourceBaseList) { for (String repos : sourceBaseList) { if (repos.endsWith(".zip") || repos.endsWith(".jar") || repos.endsWith(".z0p.gz")) { // Zip or jar archive try { if (repos.startsWith("http:") || repos.startsWith("https:") || repos.startsWith("file:")) { String url = SystemProperties.rewriteURLAccordingToProperties(repos); repositoryList.add(makeInMemorySourceRepository(url)); } else repositoryList.add(new ZipSourceRepository(new ZipFile(repos))); } catch (IOException e) { // Ignored - we won't use this archive AnalysisContext.logError("Unable to load " + repos, e); } } else { File dir = new File(repos); if (dir.canRead() && dir.isDirectory()) repositoryList.add(new DirectorySourceRepository(repos)); else { AnalysisContext.logError("Unable to load " + repos); } } } } /** * Open an input stream on a source file in given package. * * @param packageName the name of the package containing the class whose source file is given * @param fileName the unqualified name of the source file * @return an InputStream on the source file * @throws IOException if a matching source file cannot be found */ public InputStream openSource(String packageName, String fileName) throws IOException { SourceFile sourceFile = findSourceFile(packageName, fileName); return sourceFile.getInputStream(); } public InputStream openSource(SourceLineAnnotation source) throws IOException { SourceFile sourceFile = findSourceFile(source); return sourceFile.getInputStream(); } public SourceFile findSourceFile(SourceLineAnnotation source) throws IOException { return findSourceFile(source.getPackageName(), getOrGuessSourceFile(source)); } /** * Open a source file in given package. * * @param packageName the name of the package containing the class whose source file is given * @param fileName the unqualified name of the source file * @return the source file * @throws IOException if a matching source file cannot be found */ public SourceFile findSourceFile(String packageName, String fileName) throws IOException { // On windows the fileName specification is different between a file in // a directory tree, and a // file in a zip file. In a directory tree the separator used is '\', // while in a zip it's '/' // Therefore for each repository figure out what kind it is and use the // appropriate separator. // In all practicality, this code could just use the hardcoded '/' char, // as windows can open // files with this separator, but to allow for the mythical 'other' // platform that uses an // alternate separator, make a distinction String platformName = getPlatformName(packageName, fileName); String canonicalName = getCanonicalName(packageName, fileName); // Is the file in the cache already? Always cache it with the canonical // name SourceFile sourceFile = cache.get(canonicalName); if (sourceFile != null) return sourceFile; // Find this source file, add its data to the cache if (DEBUG) System.out.println("Trying " + fileName + " in package " + packageName + "..."); // Query each element of the source path to find the requested source // file for (SourceRepository repos : repositoryList) { if (repos instanceof BlockingSourceRepository && !((BlockingSourceRepository) repos).isReady()) continue; fileName = repos.isPlatformDependent() ? platformName : canonicalName; if (DEBUG) System.out.println("Looking in " + repos + " for " + fileName); if (repos.contains(fileName)) { // Found it sourceFile = new SourceFile(repos.getDataSource(fileName)); cache.put(canonicalName, sourceFile); // always cache with // canonicalName return sourceFile; } } throw new FileNotFoundException("Can't find source file " + fileName); } /** * @param packageName * @param fileName * @return */ public static String getPlatformName(String packageName, String fileName) { String platformName = packageName.replace('.', File.separatorChar) + (packageName.length() > 0 ? File.separator : "") + fileName; return platformName; } public static String getPlatformName(SourceLineAnnotation source) { return getPlatformName(source.getPackageName(), getOrGuessSourceFile(source)); } public static String getCanonicalName(SourceLineAnnotation source) { return getCanonicalName(source.getPackageName(), getOrGuessSourceFile(source)); } /** * @param packageName * @param fileName * @return */ public static String getCanonicalName(String packageName, String fileName) { String canonicalName = packageName.replace('.', '/') + (packageName.length() > 0 ? "/" : "") + fileName; return canonicalName; } public static String getOrGuessSourceFile(SourceLineAnnotation source) { if (source.isSourceFileKnown()) return source.getSourceFile(); String baseClassName = source.getClassName(); int i = baseClassName.lastIndexOf('.'); baseClassName = baseClassName.substring(i + 1); int j = baseClassName.indexOf("$"); if (j >= 0) baseClassName = baseClassName.substring(0, j); return baseClassName + ".java"; } public boolean hasSourceFile(SourceLineAnnotation source) { return hasSourceFile(source.getPackageName(), getOrGuessSourceFile(source)); } public boolean hasSourceFile(String packageName, String fileName) { // On windows the fileName specification is different between a file in // a directory tree, and a // file in a zip file. In a directory tree the separator used is '\', // while in a zip it's '/' // Therefore for each repository figure out what kind it is and use the // appropriate separator. // In all practicality, this code could just use the hardcoded '/' char, // as windows can open // files with this separator, but to allow for the mythical 'other' // platform that uses an // alternate separator, make a distinction // Create a fully qualified source filename using the package name for // both directories and zips String platformName = getPlatformName(packageName, fileName); String canonicalName = getCanonicalName(packageName, fileName); // Is the file in the cache already? Always cache it with the canonical // name SourceFile sourceFile = cache.get(canonicalName); if (sourceFile != null) return true; // Find this source file, add its data to the cache if (DEBUG) System.out.println("Trying " + fileName + " in package " + packageName + "..."); // Query each element of the source path to find the requested source // file for (SourceRepository repos : repositoryList) { if (repos instanceof BlockingSourceRepository && !((BlockingSourceRepository) repos).isReady()) continue; fileName = repos.isPlatformDependent() ? platformName : canonicalName; if (DEBUG) System.out.println("Looking in " + repos + " for " + fileName); if (repos.contains(fileName)) { return true; } } return false; } /** @param project */ private void setProject(Project project) { this.project = project; repositoryList = new LinkedList<SourceRepository>(); cache = new Cache(); setSourceBaseList(project.getResolvedSourcePaths()); } }
public CallToUnconditionalThrower(BugReporter bugReporter) { this.bugReporter = bugReporter; testingEnabled = SystemProperties.getBoolean("report_TESTING_pattern_in_standard_detectors"); }
/** * Find unsatisfied obligations in Java methods. Examples: open streams, open database connections, * etc. * * <p>See Weimer and Necula, <a href="http://doi.acm.org/10.1145/1028976.1029011" >Finding and * preventing run-time error handling mistakes</a>, OOPSLA 2004. * * @author David Hovemeyer */ public class FindUnsatisfiedObligation extends CFGDetector { private static final boolean DEBUG = SystemProperties.getBoolean("oa.debug"); private static final String DEBUG_METHOD = SystemProperties.getProperty("oa.method"); private static final boolean DEBUG_FP = SystemProperties.getBoolean("oa.debug.fp"); /** * Compute possible obligation transfers as a way of suppressing false positives due to "wrapper" * objects. Not quite ready for prime time. */ private static final boolean COMPUTE_TRANSFERS = SystemProperties.getBoolean("oa.transfers", true); /** * Report path information from point of resource creation to CFG exit. This makes the reported * warning a lot easier to understand. */ private static final boolean REPORT_PATH = SystemProperties.getBoolean("oa.reportpath", true); private static final boolean REPORT_PATH_DEBUG = SystemProperties.getBoolean("oa.reportpath.debug"); /** Report the final obligation set as part of the BugInstance. */ private static final boolean REPORT_OBLIGATION_SET = SystemProperties.getBoolean("oa.report.obligationset", true); private final BugReporter bugReporter; private ObligationPolicyDatabase database; public FindUnsatisfiedObligation(BugReporter bugReporter) { this.bugReporter = bugReporter; IAnalysisCache analysisCache = Global.getAnalysisCache(); database = analysisCache.getDatabase(ObligationPolicyDatabase.class); } @Override public void visitClass(ClassDescriptor classDescriptor) throws CheckedAnalysisException { IAnalysisCache analysisCache = Global.getAnalysisCache(); ObligationFactory factory = database.getFactory(); JavaClass jclass = analysisCache.getClassAnalysis(JavaClass.class, classDescriptor); for (Constant c : jclass.getConstantPool().getConstantPool()) { if (c instanceof ConstantNameAndType) { ConstantNameAndType cnt = (ConstantNameAndType) c; String signature = cnt.getSignature(jclass.getConstantPool()); if (factory.signatureInvolvesObligations(signature)) { super.visitClass(classDescriptor); return; } } else if (c instanceof ConstantClass) { String className = ((ConstantClass) c).getBytes(jclass.getConstantPool()); if (factory.signatureInvolvesObligations(className)) { super.visitClass(classDescriptor); return; } } } if (DEBUG) System.out.println(classDescriptor + " isn't interesting for obligation analysis"); } @Override protected void visitMethodCFG(MethodDescriptor methodDescriptor, CFG cfg) throws CheckedAnalysisException { MethodChecker methodChecker = new MethodChecker(methodDescriptor, cfg); methodChecker.analyzeMethod(); } /** * Helper class to keep track of possible obligation transfers observed along paths where an * obligation appears to be leaked. */ private static class PossibleObligationTransfer { Obligation consumed, produced; public PossibleObligationTransfer(@Nonnull Obligation consumed, @Nonnull Obligation produced) { this.consumed = consumed; this.produced = produced; } /** * Determine whether the state has "balanced" obligation counts for the consumed and produced * Obligation types. * * @param state a State * @return true if the obligation counts are balanced, false otherwise */ private boolean balanced(State state) { int consumedCount = state.getObligationSet().getCount(consumed.getId()); int producedCount = state.getObligationSet().getCount(produced.getId()); return (consumedCount + producedCount == 0) && (consumedCount == 1 || producedCount == 1); } private boolean matches(Obligation possiblyLeakedObligation) { return consumed.equals(possiblyLeakedObligation) || produced.equals(possiblyLeakedObligation); } @Override public String toString() { return consumed + " -> " + produced; } } /** * A helper class to check a single method for unsatisfied obligations. Avoids having to pass * millions of parameters to each method (type dataflow, null value dataflow, etc.). */ private class MethodChecker { MethodDescriptor methodDescriptor; CFG cfg; IAnalysisCache analysisCache; ObligationDataflow dataflow; ConstantPoolGen cpg; TypeDataflow typeDataflow; Subtypes2 subtypes2; XMethod xmethod; MethodChecker(MethodDescriptor methodDescriptor, CFG cfg) { this.methodDescriptor = methodDescriptor; this.cfg = cfg; } public void analyzeMethod() throws CheckedAnalysisException { if (DEBUG_METHOD != null && !methodDescriptor.getName().equals(DEBUG_METHOD)) { return; } if (DEBUG) { System.out.println("*** Analyzing method " + methodDescriptor); } xmethod = XFactory.createXMethod(methodDescriptor); analysisCache = Global.getAnalysisCache(); // // Execute the obligation dataflow analysis // try { dataflow = analysisCache.getMethodAnalysis(ObligationDataflow.class, methodDescriptor); } catch (ObligationAcquiredOrReleasedInLoopException e) { // It is not possible to analyze this method. if (DEBUG) { System.out.println( "FindUnsatisifedObligation: " + methodDescriptor + ": " + e.getMessage()); } return; } // // Additional analyses // needed these to apply the false-positive // suppression heuristics. // cpg = analysisCache.getClassAnalysis( ConstantPoolGen.class, methodDescriptor.getClassDescriptor()); typeDataflow = analysisCache.getMethodAnalysis(TypeDataflow.class, methodDescriptor); subtypes2 = Global.getAnalysisCache().getDatabase(Subtypes2.class); // // Main loop: looking at the StateSet at the exit block of the CFG, // see if there are any states with nonempty obligation sets. // Map<Obligation, State> leakedObligationMap = new HashMap<Obligation, State>(); StateSet factAtExit = dataflow.getResultFact(cfg.getExit()); for (Iterator<State> i = factAtExit.stateIterator(); i.hasNext(); ) { State state = i.next(); checkStateForLeakedObligations(state, leakedObligationMap); } // // Report a separate BugInstance for each Obligation,State pair. // (Two different obligations may be leaked in the same state.) // for (Map.Entry<Obligation, State> entry : leakedObligationMap.entrySet()) { Obligation obligation = entry.getKey(); State state = entry.getValue(); reportWarning(obligation, state, factAtExit); } // TODO: closing of nonexistent resources } private void checkStateForLeakedObligations( State state, Map<Obligation, State> leakedObligationMap) throws IllegalStateException { if (DEBUG) { Path path = state.getPath(); if (path.getLength() > 0 && path.getBlockIdAt(path.getLength() - 1) != cfg.getExit().getLabel()) { throw new IllegalStateException( "path " + path + " at cfg exit has no label for exit block"); } } for (int id = 0; id < database.getFactory().getMaxObligationTypes(); ++id) { Obligation obligation = database.getFactory().getObligationById(id); // If the raw count produced by the analysis // for this obligation type is 0, // assume everything is ok on this state's path. int rawLeakCount = state.getObligationSet().getCount(id); if (rawLeakCount == 0) { continue; } // Apply the false-positive suppression heuristics int leakCount = getAdjustedLeakCount(state, id); if (leakCount > 0) { leakedObligationMap.put(obligation, state); } // TODO: if the leak count is less than 0, then a nonexistent // resource was closed } } private void reportWarning(Obligation obligation, State state, StateSet factAtExit) { String className = obligation.getClassName(); if (methodDescriptor.isStatic() && methodDescriptor.getName().equals("main") && methodDescriptor.getSignature().equals("([Ljava/lang/String;)V") && (className.contains("InputStream") || className.contains("Reader") || factAtExit.isOnExceptionPath())) { // Don't report unclosed input streams and readers in main() // methods return; } String bugPattern = factAtExit.isOnExceptionPath() ? "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE" : "OBL_UNSATISFIED_OBLIGATION"; BugInstance bugInstance = new BugInstance(FindUnsatisfiedObligation.this, bugPattern, NORMAL_PRIORITY) .addClassAndMethod(methodDescriptor) .addClass(className) .describe("CLASS_REFTYPE"); // Report how many instances of the obligation are remaining bugInstance .addInt(state.getObligationSet().getCount(obligation.getId())) .describe(IntAnnotation.INT_OBLIGATIONS_REMAINING); // Add source line information annotateWarningWithSourceLineInformation(state, obligation, bugInstance); if (REPORT_OBLIGATION_SET) { bugInstance .addString(state.getObligationSet().toString()) .describe(StringAnnotation.REMAINING_OBLIGATIONS_ROLE); } bugReporter.reportBug(bugInstance); } private void annotateWarningWithSourceLineInformation( State state, Obligation obligation, BugInstance bugInstance) { // The reportPath() method currently does all reporting // of source line information. if (REPORT_PATH) { reportPath(bugInstance, obligation, state); } } /** * Helper class to apply the false-positive suppression heuristics along a Path where an * obligation leak might have occurred. */ private class PostProcessingPathVisitor implements PathVisitor { Obligation possiblyLeakedObligation; State state; int adjustedLeakCount; BasicBlock curBlock; boolean couldNotAnalyze; List<PossibleObligationTransfer> transferList; public PostProcessingPathVisitor(Obligation possiblyLeakedObligation /* * , * int * initialLeakCount */, State state) { this.possiblyLeakedObligation = possiblyLeakedObligation; this.state = state; this.adjustedLeakCount = state.getObligationSet().getCount(possiblyLeakedObligation.getId()); if (COMPUTE_TRANSFERS) { this.transferList = new LinkedList<PossibleObligationTransfer>(); } } public int getAdjustedLeakCount() { return adjustedLeakCount; } public boolean couldNotAnalyze() { return couldNotAnalyze; } @Override public void visitBasicBlock(BasicBlock basicBlock) { curBlock = basicBlock; if (COMPUTE_TRANSFERS && basicBlock == cfg.getExit()) { // We're at the CFG exit. if (adjustedLeakCount == 1) { applyPossibleObligationTransfers(); } } } @Override public void visitInstructionHandle(InstructionHandle handle) { try { Instruction ins = handle.getInstruction(); short opcode = ins.getOpcode(); if (DEBUG) System.out.printf("%3d %s%n", handle.getPosition(), Constants.OPCODE_NAMES[opcode]); if (opcode == Constants.PUTFIELD || opcode == Constants.PUTSTATIC || opcode == Constants.ARETURN) { // // A value is being assigned to a field or returned from // the method. // Location loc = new Location(handle, curBlock); TypeFrame typeFrame = typeDataflow.getFactAtLocation(loc); if (!typeFrame.isValid()) { // dead code? couldNotAnalyze = true; } Type tosType = typeFrame.getTopValue(); if (tosType instanceof ObjectType && isPossibleInstanceOfObligationType( subtypes2, (ObjectType) tosType, possiblyLeakedObligation.getType())) { // Remove one obligation of this type adjustedLeakCount--; if (DEBUG) System.out.println( "removing obligation to close " + tosType + " at " + handle.getPosition()); } } if (COMPUTE_TRANSFERS && ins instanceof InvokeInstruction) { checkForPossibleObligationTransfer((InvokeInstruction) ins, handle); } } catch (ClassNotFoundException e) { bugReporter.reportMissingClass(e); couldNotAnalyze = true; } catch (DataflowAnalysisException e) { couldNotAnalyze = true; } } private void applyPossibleObligationTransfers() { // // See if we recorded any possible obligation transfers // that might have created a "wrapper" object. // In many cases, it is correct to close either // the "wrapped" or "wrapper" object. // So, if we see a possible transfer, and we see // a +1/-1 obligation count for the pair // (consumed and produced obligation types), // rather than 0/0, // then we will assume that which resource was closed // (wrapper or wrapped) was the opposite of what // we expected. // for (PossibleObligationTransfer transfer : transferList) { if (DEBUG_FP) { System.out.println("Checking possible transfer " + transfer + "..."); } boolean matches = transfer.matches(possiblyLeakedObligation); if (DEBUG_FP) { System.out.println(" matches: " + possiblyLeakedObligation); } if (matches) { boolean balanced = transfer.balanced(state); if (DEBUG_FP) { System.out.println(" balanced: " + balanced + " in " + state.getObligationSet()); } if (balanced) { if (DEBUG_FP) { System.out.println( " Suppressing path because " + "a transfer appears to result in balanced " + "outstanding obligations"); } adjustedLeakCount = 0; break; } } } } private void checkForPossibleObligationTransfer( InvokeInstruction inv, InstructionHandle handle) throws ClassNotFoundException { // // We will assume that a method invocation might transfer // an obligation from one type to another if // 1. either // - it's a constructor where the constructed // type and exactly one param type // are obligation types, or // - it's a method where the return type and // exactly one param type are obligation types // 2. at least one instance of the resource "consumed" // by the transfer exists at the point of the transfer. // E.g., if we see a transfer of InputStream->Reader, // there must be an instance of InputStream at // the transfer point. // if (DEBUG_FP) { System.out.println("Checking " + handle + " as possible obligation transfer...:"); } // Find the State which is a prefix of the error state // at the location of this (possible) transfer. State transferState = getTransferState(handle); if (transferState == null) { if (DEBUG_FP) { System.out.println("No transfer state???"); } return; } String methodName = inv.getMethodName(cpg); Type producedType = methodName.equals("<init>") ? inv.getReferenceType(cpg) : inv.getReturnType(cpg); if (DEBUG_FP && !(producedType instanceof ObjectType)) { System.out.println("Produced type " + producedType + " not an ObjectType"); } if (producedType instanceof ObjectType) { Obligation produced = database.getFactory().getObligationByType((ObjectType) producedType); if (DEBUG_FP && produced == null) { System.out.println("Produced type " + producedType + " not an obligation type"); } if (produced != null) { XMethod calledMethod = XFactory.createXMethod(inv, cpg); Obligation[] params = database.getFactory().getParameterObligationTypes(calledMethod); for (int i = 0; i < params.length; i++) { Obligation consumed = params[i]; if (DEBUG_FP && consumed == null) { System.out.println("Param " + i + " not an obligation type"); } if (DEBUG_FP && consumed != null && consumed.equals(produced)) { System.out.println("Consumed type is the same as produced type"); } if (consumed != null && !consumed.equals(produced)) { // See if an instance of the consumed obligation // type // exists here. if (transferState.getObligationSet().getCount(consumed.getId()) > 0) { transferList.add(new PossibleObligationTransfer(consumed, produced)); if (DEBUG_FP) { System.out.println( "===> Possible transfer of " + consumed + " to " + produced + " at " + handle); } } else if (DEBUG_FP) { System.out.println( handle + " not a transfer " + "of " + consumed + "->" + produced + " because no instances of " + consumed); System.out.println("I see " + transferState.getObligationSet()); } } } } } } @Override public void visitEdge(Edge edge) { if (DEBUG_FP) { System.out.println("visit edge " + edge); } } private State getTransferState(InstructionHandle handle) { StateSet stateSet; try { stateSet = dataflow.getFactAtLocation(new Location(handle, curBlock)); } catch (DataflowAnalysisException e) { bugReporter.logError("Error checking obligation state at " + handle, e); return null; } List<State> prefixes = stateSet.getPrefixStates(state.getPath()); if (prefixes.size() != 1) { // Could this happen? if (DEBUG_FP) { System.out.println( "at " + handle + " in " + xmethod + " found " + prefixes.size() + " states which are prefixes of error state"); } return null; } return prefixes.get(0); } } /** * Get the adjusted leak count for the given State and obligation type. Use heuristics to * account for: * * <ul> * <li>null checks (count the number of times the supposedly leaked obligation is compared to * null, and subtract those from the leak count) * <li>field assignments (count number of times obligation type is assigned to a field, and * subtract those from the leak count) * <li>return statements (if an instance of the obligation type is returned from the method, * subtract one from leak count) * </ul> * * @return the adjusted leak count (positive if leaked obligation, negative if attempt to * release an un-acquired obligation) */ private int getAdjustedLeakCount(State state, int obligationId) { final Obligation obligation = database.getFactory().getObligationById(obligationId); Path path = state.getPath(); PostProcessingPathVisitor visitor = new PostProcessingPathVisitor(obligation, state); path.acceptVisitor(cfg, visitor); if (visitor.couldNotAnalyze()) { return 0; } else { return visitor.getAdjustedLeakCount(); } } private boolean isPossibleInstanceOfObligationType( Subtypes2 subtypes2, ObjectType type, ObjectType obligationType) throws ClassNotFoundException { // // If we're tracking, e.g., InputStream obligations, // and we see a FileInputStream reference being assigned // to a field (or returned from a method), // then the false-positive supressions heuristic should apply. // return subtypes2.isSubtype(type, obligationType); } private void reportPath( final BugInstance bugInstance, final Obligation obligation, final State state) { Path path = state.getPath(); // This PathVisitor will traverse the Path and add appropriate // SourceLineAnnotations to the BugInstance. PathVisitor visitor = new PathVisitor() { boolean sawFirstCreation; SourceLineAnnotation lastSourceLine; // = creationSourceLine; BasicBlock curBlock; @Override public void visitBasicBlock(BasicBlock basicBlock) { curBlock = basicBlock; // See if the initial instance of the leaked resource // is in the entry fact due to a @WillClose annotation. if (curBlock == cfg.getEntry()) { // Get the entry fact - it should have precisely one // state StateSet entryFact = dataflow.getResultFact(curBlock); Iterator<State> i = entryFact.stateIterator(); if (i.hasNext()) { State entryState = i.next(); if (entryState.getObligationSet().getCount(obligation.getId()) > 0) { lastSourceLine = SourceLineAnnotation.forFirstLineOfMethod(methodDescriptor); lastSourceLine.setDescription( SourceLineAnnotation.ROLE_OBLIGATION_CREATED_BY_WILLCLOSE_PARAMETER); bugInstance.add(lastSourceLine); sawFirstCreation = true; if (REPORT_PATH_DEBUG) { System.out.println( " " + obligation + " created by @WillClose parameter at " + lastSourceLine); } } } } } @Override public void visitInstructionHandle(InstructionHandle handle) { boolean isCreation = (dataflow .getAnalysis() .getActionCache() .addsObligation(curBlock, handle, obligation)); if (!sawFirstCreation && !isCreation) { return; } SourceLineAnnotation sourceLine = SourceLineAnnotation.fromVisitedInstruction( methodDescriptor, new Location(handle, curBlock)); sourceLine.setDescription( isCreation ? SourceLineAnnotation.ROLE_OBLIGATION_CREATED : SourceLineAnnotation.ROLE_PATH_CONTINUES); boolean isInteresting = (sourceLine.getStartLine() > 0) && (lastSourceLine == null || isCreation || sourceLine.getStartLine() != lastSourceLine.getStartLine()); if (REPORT_PATH_DEBUG) { System.out.println( " " + handle.getPosition() + " --> " + sourceLine + (isInteresting ? " **" : "")); } if (isInteresting) { bugInstance.add(sourceLine); lastSourceLine = sourceLine; if (isCreation) { sawFirstCreation = true; } } } @Override public void visitEdge(Edge edge) { if (REPORT_PATH_DEBUG) { System.out.println( "Edge of type " + Edge.edgeTypeToString(edge.getType()) + " to " + edge.getTarget().getLabel()); if (edge.getTarget().getFirstInstruction() != null) { System.out.println( " First instruction in target: " + edge.getTarget().getFirstInstruction()); } if (edge.getTarget().isExceptionThrower()) { System.out.println( " exception thrower for " + edge.getTarget().getExceptionThrower()); } if (edge.isExceptionEdge()) { System.out.println( " exceptions thrown: " + typeDataflow.getEdgeExceptionSet(edge)); } } } }; // Visit the Path path.acceptVisitor(cfg, visitor); } } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.Detector#report() */ public void report() { // Nothing to do here } }
/** * @author Nat Ayewah * @author William Pugh */ public class FindUnrelatedTypesInGenericContainer implements Detector { private final BugReporter bugReporter; private static final boolean DEBUG = SystemProperties.getBoolean("gc.debug"); static class Info { public Info(ClassDescriptor interfaceForCall, int argumentIndex, int typeIndex) { this.interfaceForCall = interfaceForCall; this.argumentIndex = argumentIndex; this.typeIndex = typeIndex; } final ClassDescriptor interfaceForCall; final int argumentIndex; final int typeIndex; @Override public String toString() { return String.format("[%s %d %d]", interfaceForCall, argumentIndex, typeIndex); } } /** * Map classname, methodname and signature to an int []. Each position in the int [] corresponds * to an argument in the methodSignature. For each argument i, the value at position i corresponds * to the index of the corresponding type in the class type parameters. If the argument has no * correspondence, then the value is -1. * * <p>Get the String key by calling getCollectionsMapKey() */ private final MultiMap<String, Info> callMap = new MultiMap<String, Info>(LinkedList.class); private void addCheckedCall( @DottedClassName String className, String methodName, String sig, int argumentParameterIndex, int typeParameterIndex) { ClassDescriptor c = DescriptorFactory.instance().getClassDescriptorForDottedClassName(className); String call = methodName + sig; Info info = new Info(c, argumentParameterIndex, typeParameterIndex); callMap.add(call, info); } private void addCheckedCall( @DottedClassName String className, String methodName, int typeParameterIndex) { addCheckedCall(className, methodName, "(Ljava/lang/Object;)", 0, typeParameterIndex); } public FindUnrelatedTypesInGenericContainer(BugReporter bugReporter) { this.bugReporter = bugReporter; // Collection<E> addCheckedCall(Collection.class.getName(), "contains", 0); addCheckedCall(Collection.class.getName(), "remove", 0); addCheckedCall(Collection.class.getName(), "containsAll", "(Ljava/util/Collection;)", 0, -1); addCheckedCall(Collection.class.getName(), "removeAll", "(Ljava/util/Collection;)", 0, -1); addCheckedCall(Collection.class.getName(), "retainAll", "(Ljava/util/Collection;)", 0, -1); // Dequeue<E> addCheckedCall("java.util.Deque", "removeFirstOccurrence", 0); addCheckedCall("java.util.Deque", "removeLastOccurrence", 0); // List<E> addCheckedCall(List.class.getName(), "indexOf", 0); addCheckedCall(List.class.getName(), "lastIndexOf", 0); // Vector<E> addCheckedCall(Vector.class.getName(), "indexOf", "(Ljava/lang/Object;I)", 0, 0); addCheckedCall(Vector.class.getName(), "lastIndexOf", "(Ljava/lang/Object;I)", 0, 0); // Map<K,V> addCheckedCall(Map.class.getName(), "containsKey", 0); addCheckedCall(Map.class.getName(), "containsValue", 1); addCheckedCall(Map.class.getName(), "get", 0); addCheckedCall(Map.class.getName(), "remove", 0); // Hashtable<K,V> addCheckedCall(Hashtable.class.getName(), "contains", 1); // ConcurrentHashMap<K,V> addCheckedCall(ConcurrentHashMap.class.getName(), "contains", 1); // ConcurrentMap<K,V> addCheckedCall( ConcurrentMap.class.getName(), "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( ConcurrentMap.class.getName(), "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); // Multimap<K,V> addCheckedCall( "com.google.common.collect.Multimap", "containsEntry", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( "com.google.common.collect.Multimap", "containsEntry", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); addCheckedCall("com.google.common.collect.Multimap", "containsKey", 0); addCheckedCall("com.google.common.collect.Multimap", "containsValue", 1); addCheckedCall( "com.google.common.collect.Multimap", "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( "com.google.common.collect.Multimap", "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); addCheckedCall("com.google.common.collect.Multimap", "removeAll", 0); // Cache<K,V> addCheckedCall("com.google.common.cache.Cache", "invalidate", 0); // Multiset<E> addCheckedCall("com.google.common.collect.Multiset", "count", 0); addCheckedCall("com.google.common.collect.Multiset", "remove", "(Ljava/lang/Object;I)", 0, 0); // Table<R,C,V> addCheckedCall( "com.google.common.collect.Table", "contains", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( "com.google.common.collect.Table", "contains", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); addCheckedCall("com.google.common.collect.Table", "containsRow", 0); addCheckedCall("com.google.common.collect.Table", "containsColumn", 1); addCheckedCall("com.google.common.collect.Table", "containsValue", 2); addCheckedCall( "com.google.common.collect.Table", "get", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( "com.google.common.collect.Table", "get", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); addCheckedCall( "com.google.common.collect.Table", "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 0, 0); addCheckedCall( "com.google.common.collect.Table", "remove", "(Ljava/lang/Object;Ljava/lang/Object;)", 1, 1); // Sets addCheckedCall( "com.google.common.collect.Sets", "intersection", "(Ljava/util/Set;Ljava/util/Set;)", 1, -1); addCheckedCall( "com.google.common.collect.Sets", "difference", "(Ljava/util/Set;Ljava/util/Set;)", 1, -1); addCheckedCall( "com.google.common.collect.Sets", "symmetricDifference", "(Ljava/util/Set;Ljava/util/Set;)", 1, -1); // Iterables addCheckedCall( "com.google.common.collect.Iterables", "contains", "(Ljava/lang/Iterable;Ljava/lang/Object;)", 1, 0); addCheckedCall( "com.google.common.collect.Iterables", "removeAll", "(Ljava/lang/Iterable;Ljava/util/Collection;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterables", "retainAll", "(Ljava/lang/Iterable;Ljava/util/Collection;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterables", "elementsEqual", "(Ljava/lang/Iterable;Ljava/lang/Iterable;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterables", "frequency", "(Ljava/lang/Iterable;Ljava/lang/Object;)", 1, 0); // Iterators addCheckedCall( "com.google.common.collect.Iterators", "contains", "(Ljava/util/Iterator;Ljava/lang/Object;)", 1, 0); addCheckedCall( "com.google.common.collect.Iterators", "removeAll", "(Ljava/util/Iterator;Ljava/util/Collection;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterators", "retainAll", "(Ljava/util/Iterator;Ljava/util/Collection;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterators", "elementsEqual", "(Ljava/util/Iterator;Ljava/util/Iterator;)", 1, -1); addCheckedCall( "com.google.common.collect.Iterators", "frequency", "(Ljava/util/Iterator;Ljava/lang/Object;)", 1, 0); } /** * Visit the class context * * @see edu.umd.cs.findbugs.Detector#visitClassContext(edu.umd.cs.findbugs.ba.ClassContext) */ public void visitClassContext(ClassContext classContext) { JavaClass javaClass = classContext.getJavaClass(); Method[] methodList = javaClass.getMethods(); for (Method method : methodList) { if (method.getCode() == null) continue; try { analyzeMethod(classContext, method); } catch (MethodUnprofitableException e) { assert true; // move along; nothing to see } catch (CFGBuilderException e) { String msg = "Detector " + this.getClass().getName() + " caught exception while analyzing " + javaClass.getClassName() + "." + method.getName() + " : " + method.getSignature(); bugReporter.logError(msg, e); } catch (DataflowAnalysisException e) { String msg = "Detector " + this.getClass().getName() + " caught exception while analyzing " + javaClass.getClassName() + "." + method.getName() + " : " + method.getSignature(); bugReporter.logError(msg, e); } } } /** Use this to screen out methods that do not contain invocations. */ public boolean prescreen(ClassContext classContext, Method method) { BitSet bytecodeSet = classContext.getBytecodeSet(method); return bytecodeSet != null && (bytecodeSet.get(Constants.INVOKEINTERFACE) || bytecodeSet.get(Constants.INVOKEVIRTUAL) || bytecodeSet.get(Constants.INVOKESPECIAL) || bytecodeSet.get(Constants.INVOKESTATIC) || bytecodeSet.get(Constants.INVOKENONVIRTUAL)); } /** Methods marked with the "Synthetic" attribute do not appear in the source code */ private boolean isSynthetic(Method m) { if ((m.getAccessFlags() & Constants.ACC_SYNTHETIC) != 0) return true; Attribute[] attrs = m.getAttributes(); for (Attribute attr : attrs) { if (attr instanceof Synthetic) return true; } return false; } static final LinkedHashSet<String> baseGenericTypes = new LinkedHashSet<String>(); static { baseGenericTypes.addAll( Arrays.asList( new String[] { "java.util.Map", "java.util.Collection", "java.lang.Iterable", "java.util.Iterator", "com.google.common.collect.Multimap", "com.google.common.collect.Multiset", "com.google.common.collect.Table" })); } private boolean isGenericCollection(ClassDescriptor operandClass) { String dottedClassName = operandClass.getDottedClassName(); if (baseGenericTypes.contains(dottedClassName)) return true; String found = null; for (String c : baseGenericTypes) { if (Subtypes2.instanceOf(operandClass, c)) { found = c; break; } } if (found == null) return false; if (dottedClassName.startsWith("java.util.") || dottedClassName.startsWith("com.google.common.collect.")) return true; try { XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, operandClass); String sig = xclass.getSourceSignature(); if (sig == null) return false; String typeParameter = null; List<String> split = GenericUtilities.split(sig, true); if (sig.charAt(0) == '<') { int end = sig.indexOf(':'); if (end > 0) typeParameter = sig.substring(1, end); } if (DEBUG) System.out.println(dottedClassName + " " + typeParameter + " " + split); for (String s : split) { int i = s.indexOf('<'); if (i < 0) continue; if (s.charAt(0) != 'L') throw new IllegalStateException("unexpected non signature: " + s); ClassDescriptor c = DescriptorFactory.createClassDescriptor(s.substring(1, i)); String superTypeParameter = s.substring(i + 1); if (isGenericCollection(c) && (typeParameter == null || superTypeParameter.startsWith("T" + typeParameter))) { if (DEBUG) System.out.println(operandClass + " is a subtype of " + s); return true; } } if (DEBUG) System.out.println("Not a subtype"); } catch (CheckedAnalysisException e1) { AnalysisContext.logError( "Error checking for weird generic parameterization of " + operandClass, e1); } return false; } private void analyzeMethod(ClassContext classContext, Method method) throws CFGBuilderException, DataflowAnalysisException { if (isSynthetic(method) || !prescreen(classContext, method)) return; XMethod xmethod = XFactory.createXMethod(classContext.getJavaClass(), method); if (xmethod.isSynthetic()) return; BugAccumulator accumulator = new BugAccumulator(bugReporter); CFG cfg = classContext.getCFG(method); TypeDataflow typeDataflow = classContext.getTypeDataflow(method); ValueNumberDataflow vnDataflow = classContext.getValueNumberDataflow(method); ConstantPoolGen cpg = classContext.getConstantPoolGen(); MethodGen methodGen = classContext.getMethodGen(method); if (methodGen == null) return; String fullMethodName = methodGen.getClassName() + "." + methodGen.getName(); String sourceFile = classContext.getJavaClass().getSourceFileName(); if (DEBUG) { System.out.println("\n" + fullMethodName); } // Process each instruction for (Iterator<Location> iter = cfg.locationIterator(); iter.hasNext(); ) { Location location = iter.next(); InstructionHandle handle = location.getHandle(); Instruction ins = handle.getInstruction(); // Only consider invoke instructions if (!(ins instanceof InvokeInstruction)) continue; InvokeInstruction inv = (InvokeInstruction) ins; XMethod invokedMethod = XFactory.createXMethod(inv, cpg); String invokedMethodName = invokedMethod.getName(); String argSignature = invokedMethod.getSignature(); argSignature = argSignature.substring(0, argSignature.indexOf(')') + 1); String call = invokedMethodName + argSignature; SignatureParser sigParser = new SignatureParser(inv.getSignature(cpg)); Collection<Info> collection = callMap.get(call); if (!callMap.containsKey(call)) continue; for (Info info : collection) { Subtypes2 subtypes2 = AnalysisContext.currentAnalysisContext().getSubtypes2(); if (DEBUG) System.out.println( "at " + handle.getPosition() + " Checking call to " + info.interfaceForCall + " : " + invokedMethod); try { if (!subtypes2.isSubtype(invokedMethod.getClassDescriptor(), info.interfaceForCall)) continue; } catch (ClassNotFoundException e) { if (info.interfaceForCall.getClassName().equals("java/util/Collection") && invokedMethod.getClassName().equals("com.google.common.collect.Multiset")) { assert true; // we know this is OK without needing to find definition of Multiset } else { AnalysisContext.reportMissingClass(e); continue; } } boolean allMethod; int typeArgument; if (info.typeIndex >= 0) { allMethod = false; typeArgument = info.typeIndex; } else { allMethod = true; typeArgument = -(1 + info.typeIndex); } int pos = info.argumentIndex; int lhsPos; if (inv instanceof INVOKESTATIC) lhsPos = sigParser.getSlotsFromTopOfStackForParameter(0); else lhsPos = sigParser.getTotalArgumentSize(); int stackPos = sigParser.getSlotsFromTopOfStackForParameter(pos); TypeFrame frame = typeDataflow.getFactAtLocation(location); if (!frame.isValid()) { // This basic block is probably dead continue; } Type operandType = frame.getStackValue(stackPos); if (operandType.equals(TopType.instance())) { // unreachable continue; } if (operandType.equals(NullType.instance())) { // ignore continue; } ValueNumberFrame vnFrame = vnDataflow.getFactAtLocation(location); if (!vnFrame.isValid()) { AnalysisContext.logError("Invalid value number frame in " + xmethod); continue; } ValueNumber objectVN = vnFrame.getStackValue(lhsPos); ValueNumber argVN = vnFrame.getStackValue(stackPos); if (objectVN.equals(argVN)) { String bugPattern = "DMI_COLLECTIONS_SHOULD_NOT_CONTAIN_THEMSELVES"; int priority = HIGH_PRIORITY; if (invokedMethodName.equals("removeAll")) { bugPattern = "DMI_USING_REMOVEALL_TO_CLEAR_COLLECTION"; priority = NORMAL_PRIORITY; } else if (invokedMethodName.endsWith("All")) { bugPattern = "DMI_VACUOUS_SELF_COLLECTION_CALL"; priority = NORMAL_PRIORITY; } if (invokedMethodName.startsWith("contains")) { InstructionHandle next = handle.getNext(); if (next != null) { Instruction nextIns = next.getInstruction(); if (nextIns instanceof InvokeInstruction) { XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg); if (nextMethod.getName().equals("assertFalse")) continue; } } } accumulator.accumulateBug( new BugInstance(this, bugPattern, priority) .addClassAndMethod(methodGen, sourceFile) .addCalledMethod(methodGen, (InvokeInstruction) ins) .addOptionalAnnotation( ValueNumberSourceInfo.findAnnotationFromValueNumber( method, location, objectVN, vnFrame, "INVOKED_ON")), SourceLineAnnotation.fromVisitedInstruction( classContext, methodGen, sourceFile, handle)); } // Only consider generic... Type objectType = frame.getStackValue(lhsPos); if (!(objectType instanceof GenericObjectType)) continue; GenericObjectType operand = (GenericObjectType) objectType; int expectedTypeParameters = 1; String simpleName = info.interfaceForCall.getSimpleName(); if (simpleName.toLowerCase().endsWith("map") || simpleName.equals("Hashtable")) expectedTypeParameters = 2; else if (simpleName.equals("Table")) expectedTypeParameters = 3; // ... containers if (!operand.hasParameters()) continue; if (operand.getNumParameters() != expectedTypeParameters) continue; ClassDescriptor operandClass = DescriptorFactory.getClassDescriptor(operand); if (!isGenericCollection(operandClass)) continue; if (expectedTypeParameters == 2 && Subtypes2.instanceOf(operandClass, Map.class) && !TypeFrameModelingVisitor.isStraightGenericMap(operandClass)) continue; Type expectedType; if (allMethod) expectedType = operand; else expectedType = operand.getParameterAt(typeArgument); Type actualType = frame.getStackValue(stackPos); Type equalsType = actualType; if (allMethod) { if (!(actualType instanceof GenericObjectType)) { continue; } equalsType = ((GenericObjectType) actualType).getParameterAt(typeArgument); } IncompatibleTypes matchResult = compareTypes(expectedType, actualType, allMethod); boolean parmIsObject = expectedType.getSignature().equals("Ljava/lang/Object;"); boolean selfOperation = !allMethod && operand.equals(actualType) && !parmIsObject; if (!allMethod && !parmIsObject && actualType instanceof GenericObjectType) { GenericObjectType p2 = (GenericObjectType) actualType; List<? extends ReferenceType> parameters = p2.getParameters(); if (parameters != null && parameters.equals(operand.getParameters())) selfOperation = true; } if (!selfOperation && (matchResult == IncompatibleTypes.SEEMS_OK || matchResult.getPriority() == Priorities.IGNORE_PRIORITY)) continue; if (invokedMethodName.startsWith("contains") || invokedMethodName.equals("remove")) { InstructionHandle next = handle.getNext(); if (next != null) { Instruction nextIns = next.getInstruction(); if (nextIns instanceof InvokeInstruction) { XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg); if (nextMethod.getName().equals("assertFalse")) continue; } } } else if (invokedMethodName.equals("get") || invokedMethodName.equals("remove")) { InstructionHandle next = handle.getNext(); if (next != null) { Instruction nextIns = next.getInstruction(); if (nextIns instanceof InvokeInstruction) { XMethod nextMethod = XFactory.createXMethod((InvokeInstruction) nextIns, cpg); if (nextMethod.getName().equals("assertNull")) continue; } } } boolean noisy = false; if (invokedMethodName.equals("get")) { UnconditionalValueDerefDataflow unconditionalValueDerefDataflow = classContext.getUnconditionalValueDerefDataflow(method); UnconditionalValueDerefSet unconditionalDeref = unconditionalValueDerefDataflow.getFactAtLocation(location); ValueNumberFrame vnAfter = vnDataflow.getFactAfterLocation(location); ValueNumber top = vnAfter.getTopValue(); noisy = unconditionalDeref.getValueNumbersThatAreUnconditionallyDereferenced().contains(top); } // Prepare bug report SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction( classContext, methodGen, sourceFile, handle); // Report a bug that mentions each of the failed arguments in // matches if (expectedType instanceof GenericObjectType) expectedType = ((GenericObjectType) expectedType).getUpperBound(); int priority = matchResult.getPriority(); if (!operandClass.getClassName().startsWith("java/util") && priority == Priorities.HIGH_PRIORITY) priority = Math.max(priority, Priorities.NORMAL_PRIORITY); if (TestCaseDetector.likelyTestCase(xmethod)) priority = Math.max(priority, Priorities.NORMAL_PRIORITY); else if (selfOperation) priority = Priorities.HIGH_PRIORITY; ClassDescriptor expectedClassDescriptor = DescriptorFactory.createClassOrObjectDescriptorFromSignature( expectedType.getSignature()); ClassDescriptor actualClassDescriptor = DescriptorFactory.createClassOrObjectDescriptorFromSignature(equalsType.getSignature()); ClassSummary classSummary = AnalysisContext.currentAnalysisContext().getClassSummary(); Set<XMethod> targets = null; try { targets = Hierarchy2.resolveVirtualMethodCallTargets( actualClassDescriptor, "equals", "(Ljava/lang/Object;)Z", false, false); boolean allOk = targets.size() > 0; for (XMethod m2 : targets) if (!classSummary.mightBeEqualTo(m2.getClassDescriptor(), expectedClassDescriptor)) allOk = false; if (allOk) priority += 2; } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); } String bugPattern = "GC_UNRELATED_TYPES"; BugInstance bug = new BugInstance(this, bugPattern, priority) .addClassAndMethod(methodGen, sourceFile) .addFoundAndExpectedType(actualType, expectedType) .addCalledMethod(methodGen, (InvokeInstruction) ins) .addOptionalAnnotation( ValueNumberSourceInfo.findAnnotationFromValueNumber( method, location, objectVN, vnFrame, "INVOKED_ON")) .addOptionalAnnotation( ValueNumberSourceInfo.findAnnotationFromValueNumber( method, location, argVN, vnFrame, "ARGUMENT")) .addEqualsMethodUsed(targets); if (noisy) { WarningPropertySet<WarningProperty> propertySet = new WarningPropertySet<WarningProperty>(); propertySet.addProperty(GeneralWarningProperty.NOISY_BUG); propertySet.decorateBugInstance(bug); } accumulator.accumulateBug(bug, sourceLineAnnotation); } } accumulator.reportAccumulatedBugs(); } /** * Compare to see if the argument <code>argType</code> passed to the method matches the type of * the corresponding parameter. The simplest case is when both are equal. * * <p>This is a conservative comparison - returns true if it cannot decide. If the parameter type * is a type variable (e.g. <code>T</code>) then we don't know enough (yet) to decide if they do * not match so return true. * * @param ignoreBaseType TODO */ private IncompatibleTypes compareTypes( Type expectedType, Type actualType, boolean ignoreBaseType) { // XXX equality not implemented for GenericObjectType // if (parmType.equals(argType)) return true; if (expectedType == actualType) return IncompatibleTypes.SEEMS_OK; // Compare type signatures instead String expectedString = GenericUtilities.getString(expectedType); String actualString = GenericUtilities.getString(actualType); if (expectedString.equals(actualString)) return IncompatibleTypes.SEEMS_OK; if (expectedType.equals(Type.OBJECT)) return IncompatibleTypes.SEEMS_OK; // if either type is java.lang.Object, then automatically true! // again compare strings... String objString = GenericUtilities.getString(Type.OBJECT); if (expectedString.equals(objString)) { return IncompatibleTypes.SEEMS_OK; } // get a category for each type TypeCategory expectedCat = GenericUtilities.getTypeCategory(expectedType); TypeCategory argCat = GenericUtilities.getTypeCategory(actualType); if (actualString.equals(objString) && expectedCat == TypeCategory.TYPE_VARIABLE) { return IncompatibleTypes.SEEMS_OK; } if (ignoreBaseType) { if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) { GenericObjectType parmGeneric = (GenericObjectType) expectedType; GenericObjectType argGeneric = (GenericObjectType) actualType; return compareTypeParameters(parmGeneric, argGeneric); } return IncompatibleTypes.SEEMS_OK; } if (actualType.equals(Type.OBJECT) && expectedCat == TypeCategory.ARRAY_TYPE) return IncompatibleTypes.ARRAY_AND_OBJECT; // -~- plain objects are easy if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PLAIN_OBJECT_TYPE) return IncompatibleTypes.getPriorityForAssumingCompatible(expectedType, actualType, false); if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PLAIN_OBJECT_TYPE) return IncompatibleTypes.getPriorityForAssumingCompatible( (GenericObjectType) expectedType, actualType); if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PARAMETERIZED) return IncompatibleTypes.getPriorityForAssumingCompatible( (GenericObjectType) actualType, expectedType); // -~- parmType is: "? extends Another Type" OR "? super Another Type" if (expectedCat == TypeCategory.WILDCARD_EXTENDS || expectedCat == TypeCategory.WILDCARD_SUPER) return compareTypes( ((GenericObjectType) expectedType).getExtension(), actualType, ignoreBaseType); // -~- Not handling type variables if (expectedCat == TypeCategory.TYPE_VARIABLE || argCat == TypeCategory.TYPE_VARIABLE) return IncompatibleTypes.SEEMS_OK; // -~- Array Types: compare dimensions, then base type if (expectedCat == TypeCategory.ARRAY_TYPE && argCat == TypeCategory.ARRAY_TYPE) { ArrayType parmArray = (ArrayType) expectedType; ArrayType argArray = (ArrayType) actualType; if (parmArray.getDimensions() != argArray.getDimensions()) return IncompatibleTypes.ARRAY_AND_NON_ARRAY; return compareTypes(parmArray.getBasicType(), argArray.getBasicType(), ignoreBaseType); } // If one is an Array Type and the other is not, then they // are incompatible. (We already know neither is java.lang.Object) if (expectedCat == TypeCategory.ARRAY_TYPE ^ argCat == TypeCategory.ARRAY_TYPE) { return IncompatibleTypes.ARRAY_AND_NON_ARRAY; } // -~- Parameter Types: compare base type then parameters if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) { GenericObjectType parmGeneric = (GenericObjectType) expectedType; GenericObjectType argGeneric = (GenericObjectType) actualType; // base types should be related { IncompatibleTypes result = compareTypes(parmGeneric.getObjectType(), argGeneric.getObjectType(), ignoreBaseType); if (!result.equals(IncompatibleTypes.SEEMS_OK)) return result; } return compareTypeParameters(parmGeneric, argGeneric); // XXX More to come } // If one is a Parameter Type and the other is not, then they // are incompatible. (We already know neither is java.lang.Object) if (false) { // not true. Consider class Foo extends ArrayList<String> if (expectedCat == TypeCategory.PARAMETERIZED ^ argCat == TypeCategory.PARAMETERIZED) { return IncompatibleTypes.SEEMS_OK; // fix this when we know what // we are doing here } } // -~- Wildcard e.g. List<*>.contains(...) if (expectedCat == TypeCategory.WILDCARD) // No Way to know return IncompatibleTypes.SEEMS_OK; // -~- Non Reference types // if ( parmCat == TypeCategory.NON_REFERENCE_TYPE || // argCat == TypeCategory.NON_REFERENCE_TYPE ) if (expectedType instanceof BasicType || actualType instanceof BasicType) { // this should not be possible, compiler will complain (pre 1.5) // or autobox primitive types (1.5 +) throw new IllegalArgumentException( "checking for compatibility of " + expectedType + " with " + actualType); } return IncompatibleTypes.SEEMS_OK; } private IncompatibleTypes compareTypeParameters( GenericObjectType parmGeneric, GenericObjectType argGeneric) { int p = parmGeneric.getNumParameters(); if (p != argGeneric.getNumParameters()) { return IncompatibleTypes.SEEMS_OK; } for (int x = 0; x < p; x++) { IncompatibleTypes result = compareTypes(parmGeneric.getParameterAt(x), argGeneric.getParameterAt(x), false); if (result != IncompatibleTypes.SEEMS_OK) return result; } return IncompatibleTypes.SEEMS_OK; } // old version of compare types private boolean compareTypesOld(Type parmType, Type argType) { // XXX equality not implemented for GenericObjectType // if (parmType.equals(argType)) return true; // Compare type signatures instead if (GenericUtilities.getString(parmType).equals(GenericUtilities.getString(argType))) return true; if (parmType instanceof GenericObjectType) { GenericObjectType o = (GenericObjectType) parmType; if (o.getTypeCategory() == GenericUtilities.TypeCategory.WILDCARD_EXTENDS) { return compareTypesOld(o.getExtension(), argType); } } // ignore type variables for now if (parmType instanceof GenericObjectType && !((GenericObjectType) parmType).hasParameters()) return true; if (argType instanceof GenericObjectType && !((GenericObjectType) argType).hasParameters()) return true; // Case: Both are generic containers if (parmType instanceof GenericObjectType && argType instanceof GenericObjectType) { return true; } else { // Don't consider non reference types (should not be possible) if (!(parmType instanceof ReferenceType && argType instanceof ReferenceType)) return true; // Don't consider non object types (for now) if (!(parmType instanceof ObjectType && argType instanceof ObjectType)) return true; // Otherwise, compare base types ignoring generic information try { return Repository.instanceOf( ((ObjectType) argType).getClassName(), ((ObjectType) parmType).getClassName()); } catch (ClassNotFoundException e) { } } return true; } /** * Empty * * @see edu.umd.cs.findbugs.Detector#report() */ public void report() {} }
/** * A TypeQualifierValue is a pair specifying a type qualifier annotation and a value. Each * TypeQualifierValue is effectively a different type qualifier. For example, if Foo is a type * qualifier annotation having an int value, then Foo(0), Foo(1), etc. are all different type * qualifiers which must be checked separately. * * @author William Pugh */ public class TypeQualifierValue<A extends Annotation> { public static final boolean DEBUG = SystemProperties.getBoolean("tqv.debug"); private static final ClassDescriptor EXCLUSIVE_ANNOTATION = DescriptorFactory.instance().getClassDescriptor(javax.annotation.meta.Exclusive.class); private static final ClassDescriptor EXHAUSTIVE_ANNOTATION = DescriptorFactory.instance().getClassDescriptor(javax.annotation.meta.Exhaustive.class); public final ClassDescriptor typeQualifier; public final Class<A> typeQualifierClass; public final A proxy; public final @CheckForNull Object value; private final boolean isStrict; private final boolean isExclusive; private final boolean isExhaustive; private final @CheckForNull TypeQualifierValidator<A> validator; private static final ClassLoader validatorLoader = new ValidatorClassLoader(); private TypeQualifierValue(ClassDescriptor typeQualifier, @CheckForNull Object value) { this.typeQualifier = typeQualifier; this.value = value; boolean isStrict = false; // will be set to true if this is a strict // type qualifier value boolean isExclusive = false; // will be set to true if this is an // exclusive type qualifier value boolean isExhaustive = false; // will be set to true if this is an // exhaustive type qualifier value TypeQualifierValidator<A> validator = null; Class<A> qualifierClass = null; A proxy = null; try { XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, typeQualifier); // Annotation elements appear as abstract methods in the annotation // class (interface). // So, if the type qualifier annotation has specified a default When // value, // it will appear as an abstract method called "when". XMethod whenMethod = xclass.findMethod("when", "()Ljavax/annotation/meta/When;", false); if (whenMethod == null) { isStrict = true; } for (XMethod xmethod : xclass.getXMethods()) { if (xmethod.getName().equals("value") && xmethod.getSignature().startsWith("()")) { isExhaustive = xmethod.getAnnotation(EXHAUSTIVE_ANNOTATION) != null; if (isExhaustive) { // exhaustive qualifiers are automatically exclusive isExclusive = true; } else { // see if there is an explicit @Exclusive annotation isExclusive = xmethod.getAnnotation(EXCLUSIVE_ANNOTATION) != null; } break; } } } catch (MissingClassException e) { AnalysisContext.currentAnalysisContext() .getLookupFailureCallback() .reportMissingClass(e.getClassNotFoundException()); } catch (CheckedAnalysisException e) { AnalysisContext.logError( "Error looking up annotation class " + typeQualifier.toDottedClassName(), e); } this.isStrict = isStrict; this.isExclusive = isExclusive; this.isExhaustive = isExhaustive; ClassDescriptor checkerName = DescriptorFactory.createClassDescriptor(typeQualifier.getClassName() + "$Checker"); try { Global.getAnalysisCache().getClassAnalysis(ClassData.class, checkerName); // found it. // System.out.println(checkerName); SecurityManager m = System.getSecurityManager(); if (m == null) System.setSecurityManager(new ValidationSecurityManager()); Class<?> c = validatorLoader.loadClass(checkerName.getDottedClassName()); if (TypeQualifierValidator.class.isAssignableFrom(c)) { Class<? extends TypeQualifierValidator> checkerClass = c.asSubclass(TypeQualifierValidator.class); validator = getValidator(checkerClass); qualifierClass = getQualifierClass(typeQualifier); InvocationHandler handler = new InvocationHandler() { public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { if (arg1.getName() == "value") return TypeQualifierValue.this.value; throw new UnsupportedOperationException("Can't handle " + arg1); } }; proxy = qualifierClass.cast( Proxy.newProxyInstance(validatorLoader, new Class[] {qualifierClass}, handler)); } } catch (ClassNotFoundException e) { assert true; // ignore } catch (CheckedAnalysisException e) { assert true; // ignore } catch (Exception e) { AnalysisContext.logError("Unable to construct type qualifier checker " + checkerName, e); } catch (Throwable e) { AnalysisContext.logError( "Unable to construct type qualifier checker " + checkerName + " due to " + e.getClass().getSimpleName() + ":" + e.getMessage()); } this.validator = validator; this.typeQualifierClass = qualifierClass; this.proxy = proxy; } /** * @param checkerClass * @return * @throws InstantiationException * @throws IllegalAccessException */ @SuppressWarnings("unchecked") private TypeQualifierValidator<A> getValidator( Class<? extends TypeQualifierValidator> checkerClass) throws InstantiationException, IllegalAccessException { return checkerClass.newInstance(); } /** * @param typeQualifier * @return * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private Class<A> getQualifierClass(ClassDescriptor typeQualifier) throws ClassNotFoundException { return (Class<A>) validatorLoader.loadClass(typeQualifier.getDottedClassName()); } static class Data { /** Cache in which constructed TypeQualifierValues are interned. */ DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>> typeQualifierMap = new DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>>(); /** Set of all known TypeQualifierValues. */ Set<TypeQualifierValue<?>> allKnownTypeQualifiers = new HashSet<TypeQualifierValue<?>>(); } private static ThreadLocal<Data> instance = new ThreadLocal<Data>() { @Override protected Data initialValue() { return new Data(); } }; public static void clearInstance() { instance.remove(); } public boolean canValidate(Object constantValue) { if (validator == null) return false; return true; } private static final InheritableThreadLocal<AtomicBoolean> performingValidation = new InheritableThreadLocal<AtomicBoolean>() { @Override protected AtomicBoolean initialValue() { return new AtomicBoolean(); } }; static final class ValidationSecurityManager extends SecurityManager { @Override public void checkPermission(Permission perm) { // System.out.println("Checking " + perm); if (performingValidation.get().get()) throw new SecurityException("not permissions granted while performing JSR-305 validation"); } @Override public void checkPermission(Permission perm, Object context) { if (performingValidation.get().get()) throw new SecurityException("not permissions granted while performing JSR-305 validation"); } } public When validate(Object constantValue) { if (validator == null) throw new IllegalStateException("No validator"); IAnalysisCache analysisCache = Global.getAnalysisCache(); Profiler profiler = analysisCache.getProfiler(); profiler.start(validator.getClass()); AtomicBoolean performing = performingValidation.get(); try { if (!performing.compareAndSet(false, true)) { throw new IllegalStateException("recursive validation"); } return validator.forConstantValue(proxy, constantValue); } catch (Exception e) { AnalysisContext.logError( "Error executing custom validator for " + typeQualifier + " " + constantValue, e); return When.UNKNOWN; } finally { if (!performing.compareAndSet(true, false)) { throw new IllegalStateException("performingValidation not set when validation completes"); } profiler.end(validator.getClass()); } } /** * Given a ClassDescriptor/value pair, return the interned TypeQualifierValue representing that * pair. * * @param desc a ClassDescriptor denoting a type qualifier annotation * @param value a value * @return an interned TypeQualifierValue object */ public static @Nonnull TypeQualifierValue<?> getValue(ClassDescriptor desc, Object value) { DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue<?>> map = instance.get().typeQualifierMap; TypeQualifierValue<?> result = map.get(desc, value); if (result != null) return result; result = new TypeQualifierValue(desc, value); map.put(desc, value, result); instance.get().allKnownTypeQualifiers.add(result); return result; } @SuppressWarnings("unchecked") public static @Nonnull <A extends Annotation> TypeQualifierValue<A> getValue( Class<A> clazz, Object value) { return (TypeQualifierValue<A>) getValue(DescriptorFactory.createClassDescriptor(clazz), value); } /** * Get Collection of all known TypeQualifierValues. * * @return Collection of all known TypeQualifierValues */ public static Collection<TypeQualifierValue<?>> getAllKnownTypeQualifiers() { return Collections.unmodifiableSet(instance.get().allKnownTypeQualifiers); } /** * Get the "complementary" TypeQualifierValues for given exclusive type qualifier. * * @param tqv a type qualifier (which must be exclusive) * @return Collection of complementary exclusive type qualifiers */ public static Collection<TypeQualifierValue> getComplementaryExclusiveTypeQualifierValue( TypeQualifierValue tqv) { assert tqv.isExclusiveQualifier(); LinkedList<TypeQualifierValue> result = new LinkedList<TypeQualifierValue>(); for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) { // // Any TypeQualifierValue with the same // annotation class but a different value is a complementary // type qualifier. // if (t.typeQualifier.equals(tqv.typeQualifier) && !Util.nullSafeEquals(t.value, tqv.value)) { result.add(t); } } return result; } /** * Determine whether or not given TypeQualifierValue has multiple variants. I.e., if Color is a * type qualifier having values RED, GREEN, and BLUE, then there are 3 variants, Color(RED), * Color(GREEN), and COLOR(BLUE). * * @param tqv a TypeQualifierValue * @return true if there are multiple variants of this type qualifier, false otherwise */ public static boolean hasMultipleVariants(TypeQualifierValue tqv) { int count = 0; for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) { if (t.typeQualifier.equals(tqv.typeQualifier)) { count++; } } return count > 1; } /** * Get the ClassDescriptor which specifies the type qualifier annotation. * * @return ClassDescriptor which specifies the type qualifier annotation */ public ClassDescriptor getTypeQualifierClassDescriptor() { return typeQualifier; } /** * Return whether or not this TypeQualifierValue denotes a strict qualifier. * * @return true if type qualifier is strict, false otherwise */ public boolean isStrictQualifier() { return isStrict; } /** * Return whether or not this TypeQualifierValue denotes an exclusive qualifier. * * @return true if type qualifier is exclusive, false otherwise */ public boolean isExclusiveQualifier() { return isExclusive; } /** * Return whether or not this TypeQualifierValue denotes an exhaustive qualifier. * * @return true if type qualifier is exhaustive, false otherwise */ public boolean isExhaustiveQualifier() { return isExhaustive; } @Override public int hashCode() { int result = typeQualifier.hashCode(); if (value != null) result += 37 * value.hashCode(); return result; } @Override public boolean equals(Object o) { if (!(o instanceof TypeQualifierValue)) return false; TypeQualifierValue other = (TypeQualifierValue) o; return typeQualifier.equals(other.typeQualifier) && Util.nullSafeEquals(value, other.value); } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(typeQualifier.toString()); if (value != null) { buf.append(':'); buf.append(value.toString()); } return buf.toString(); } }