/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2006-2007 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.classfile.impl; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ConstantPoolGen; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.ClassContext; import edu.umd.cs.findbugs.ba.Debug; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.IAnalysisCache; import edu.umd.cs.findbugs.classfile.IAnalysisEngine; import edu.umd.cs.findbugs.classfile.IClassAnalysisEngine; import edu.umd.cs.findbugs.classfile.IClassPath; import edu.umd.cs.findbugs.classfile.IDatabaseFactory; import edu.umd.cs.findbugs.classfile.IErrorLogger; import edu.umd.cs.findbugs.classfile.IMethodAnalysisEngine; import edu.umd.cs.findbugs.classfile.MethodDescriptor; import edu.umd.cs.findbugs.classfile.UncheckedAnalysisException; import edu.umd.cs.findbugs.log.Profiler; import edu.umd.cs.findbugs.util.MapCache; /** * Implementation of IAnalysisCache. * This object is responsible for registering class and method analysis engines * and caching analysis results. * * @author David Hovemeyer */ public class AnalysisCache implements IAnalysisCache { /** * */ private static final int MAX_JAVACLASS_RESULTS_TO_CACHE = 5000; private static final int MAX_CONSTANT_POOL_GEN_RESULTS_TO_CACHE = 500; /** * Maximum number of class analysis results to cache. */ private static final int MAX_CLASS_RESULTS_TO_CACHE = 5000; private static final boolean ASSERTIONS_ENABLED = SystemProperties.ASSERTIONS_ENABLED; // Fields private final IClassPath classPath; private final BugReporter bugReporter; private final Map<Class<?>, IClassAnalysisEngine<?>> classAnalysisEngineMap; private final Map<Class<?>, IMethodAnalysisEngine<?>> methodAnalysisEngineMap; private final Map<Class<?>, IDatabaseFactory<?>> databaseFactoryMap; private final Map<Class<?>, Map<ClassDescriptor, Object>> classAnalysisMap; private final Map<Class<?>, Object> databaseMap; private final Map<?,?> analysisLocals = Collections.synchronizedMap(new HashMap<Object,Object>()); public final Map<?, ?> getAnalysisLocals() { return analysisLocals; } static class AbnormalAnalysisResult { final CheckedAnalysisException checkedAnalysisException; final RuntimeException runtimeException; final boolean isNull; AbnormalAnalysisResult(CheckedAnalysisException checkedAnalysisException) { this.checkedAnalysisException = checkedAnalysisException; this.runtimeException = null; isNull = false; } AbnormalAnalysisResult(RuntimeException runtimeException) { this.runtimeException = runtimeException; this.checkedAnalysisException = null; isNull = false; } AbnormalAnalysisResult() { this.isNull = true; this.checkedAnalysisException = null; this.runtimeException = null; } public Object returnOrThrow() throws CheckedAnalysisException { if (isNull) { return null; } else if (runtimeException != null) { // runtimeException.fillInStackTrace(); throw runtimeException; } else if (checkedAnalysisException != null) { // checkedAnalysisException.fillInStackTrace(); throw checkedAnalysisException; } throw new IllegalStateException("It has to be something"); } } static final AbnormalAnalysisResult NULL_ANALYSIS_RESULT = new AbnormalAnalysisResult(); @SuppressWarnings("unchecked") static <E> E checkedCast(Class<E> analysisClass, Object o) { if (ASSERTIONS_ENABLED) { return analysisClass.cast(o); } return (E) o; } /** * Constructor. * * @param classPath the IClassPath to load resources from * @param errorLogger the IErrorLogger */ AnalysisCache(IClassPath classPath, BugReporter errorLogger) { this.classPath = classPath; this.bugReporter = errorLogger; this.classAnalysisEngineMap = new HashMap<Class<?>, IClassAnalysisEngine<?>>(); this.methodAnalysisEngineMap = new HashMap<Class<?>, IMethodAnalysisEngine<?>>(); this.databaseFactoryMap = new HashMap<Class<?>, IDatabaseFactory<?>>(); this.classAnalysisMap = new HashMap<Class<?>, Map<ClassDescriptor,Object>>(); this.databaseMap = new HashMap<Class<?>, Object>(); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getClassPath() */ public IClassPath getClassPath() { return classPath; } public void purgeAllMethodAnalysis() { // System.out.println("ZZZ : purging all method analyses"); try { Map<ClassDescriptor, ClassContext> map = getAllClassAnalysis(ClassContext.class); Collection<?> allClassContexts = map.values(); for(Object c : allClassContexts) { if (c instanceof ClassContext) { ((ClassContext)c).purgeAllMethodAnalyses(); } } } catch (ClassCastException e) { AnalysisContext.logError("Unable to purge method analysis" , e); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Unable to purge method analysis" , e); } } @SuppressWarnings("unchecked") private <E> Map<ClassDescriptor, E> getAllClassAnalysis(Class<E> analysisClass) throws CheckedAnalysisException { Map<ClassDescriptor, Object> descriptorMap = findOrCreateDescriptorMap(classAnalysisMap, (Map)classAnalysisEngineMap, analysisClass); return (Map<ClassDescriptor, E>) descriptorMap; } public void purgeClassAnalysis(Class<?> analysisClass) { classAnalysisMap.remove(analysisClass); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getClassAnalysis(java.lang.Class, edu.umd.cs.findbugs.classfile.ClassDescriptor) */ @SuppressWarnings("unchecked") public <E> E getClassAnalysis(Class<E> analysisClass, ClassDescriptor classDescriptor) throws CheckedAnalysisException { if (classDescriptor == null) { throw new NullPointerException("classDescriptor is null"); } // Get the descriptor->result map for this analysis class, // creating if necessary Map<ClassDescriptor, Object> descriptorMap = findOrCreateDescriptorMap(classAnalysisMap, (Map)classAnalysisEngineMap, analysisClass); // See if there is a cached result in the descriptor map Object analysisResult = descriptorMap.get(classDescriptor); if (analysisResult == null) { // No cached result - compute (or recompute) IAnalysisEngine<ClassDescriptor, E> engine = (IAnalysisEngine<ClassDescriptor, E>) classAnalysisEngineMap.get(analysisClass); if (engine == null) { throw new IllegalArgumentException( "No analysis engine registered to produce " + analysisClass.getName()); } Profiler profiler = getProfiler(); // Perform the analysis try { profiler.start(engine.getClass()); analysisResult = engine.analyze(this, classDescriptor); // If engine returned null, we need to construct // an AbnormalAnalysisResult object to record that fact. // Otherwise we will try to recompute the value in // the future. if (analysisResult == null) { analysisResult = NULL_ANALYSIS_RESULT; } } catch (CheckedAnalysisException e) { // Exception - make note // Andrei: e.getStackTrace() cannot be null, but getter clones the stack... // if (e.getStackTrace() == null) // e.fillInStackTrace(); analysisResult = new AbnormalAnalysisResult(e); } catch (RuntimeException e) { // Exception - make note // Andrei: e.getStackTrace() cannot be null, but getter clones the stack... // if (e.getStackTrace() == null) // e.fillInStackTrace(); analysisResult = new AbnormalAnalysisResult(e); } finally { profiler.end(engine.getClass()); } // Save the result descriptorMap.put(classDescriptor, analysisResult); } // Abnormal analysis result? if (analysisResult instanceof AbnormalAnalysisResult) { return checkedCast(analysisClass,((AbnormalAnalysisResult) analysisResult).returnOrThrow()); } return checkedCast(analysisClass,analysisResult); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#probeClassAnalysis(java.lang.Class, edu.umd.cs.findbugs.classfile.ClassDescriptor) */ public <E> E probeClassAnalysis(Class<E> analysisClass, ClassDescriptor classDescriptor) { Map<ClassDescriptor, Object> descriptorMap = classAnalysisMap.get(analysisClass); if (descriptorMap == null) { return null; } return checkedCast(analysisClass, descriptorMap.get(classDescriptor)); } String hex(Object o) { return Integer.toHexString(System.identityHashCode(o)); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getMethodAnalysis(java.lang.Class, edu.umd.cs.findbugs.classfile.MethodDescriptor) */ public <E> E getMethodAnalysis(Class<E> analysisClass, MethodDescriptor methodDescriptor) throws CheckedAnalysisException { if (methodDescriptor == null) { throw new NullPointerException("methodDescriptor is null"); } ClassContext classContext = getClassAnalysis(ClassContext.class, methodDescriptor.getClassDescriptor()); Object object = classContext.getMethodAnalysis(analysisClass, methodDescriptor); if (object == null) { try { object = analyzeMethod(classContext, analysisClass, methodDescriptor); if (object == null) { object = NULL_ANALYSIS_RESULT; } } catch (RuntimeException e) { object = new AbnormalAnalysisResult(e); } catch (CheckedAnalysisException e) { object = new AbnormalAnalysisResult(e); } classContext.putMethodAnalysis(analysisClass, methodDescriptor, object); } if (Debug.VERIFY_INTEGRITY && object == null) { throw new IllegalStateException("AnalysisFactory failed to produce a result object"); } if (object instanceof AbnormalAnalysisResult) { return checkedCast(analysisClass, ((AbnormalAnalysisResult) object).returnOrThrow()); } return checkedCast(analysisClass, object); } /** * Analyze a method. * * @param classContext ClassContext storing method analysis objects for method's class * @param analysisClass class the method analysis object should belong to * @param methodDescriptor method descriptor identifying the method to analyze * @return the computed analysis object for the method * @throws CheckedAnalysisException */ @SuppressWarnings("unchecked") private <E> E analyzeMethod( ClassContext classContext, Class<E> analysisClass, MethodDescriptor methodDescriptor) throws CheckedAnalysisException { IMethodAnalysisEngine<E> engine = (IMethodAnalysisEngine<E>) methodAnalysisEngineMap.get(analysisClass); if (engine == null) { throw new IllegalArgumentException( "No analysis engine registered to produce " + analysisClass.getName()); } Profiler profiler = getProfiler(); profiler.start(engine.getClass()); try { return engine.analyze(this, methodDescriptor); } finally { profiler.end(engine.getClass()); } } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#eagerlyPutMethodAnalysis(java.lang.Class, edu.umd.cs.findbugs.classfile.MethodDescriptor, java.lang.Object) */ public <E> void eagerlyPutMethodAnalysis(Class<E> analysisClass, MethodDescriptor methodDescriptor, Object analysisObject) { try { ClassContext classContext = getClassAnalysis(ClassContext.class, methodDescriptor.getClassDescriptor()); classContext.putMethodAnalysis(analysisClass, methodDescriptor, analysisObject); } catch (CheckedAnalysisException e) { IllegalStateException ise = new IllegalStateException("Unexpected exception adding method analysis to cache"); ise.initCause(e); throw ise; } } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#purgeMethodAnalyses(edu.umd.cs.findbugs.classfile.MethodDescriptor) */ public void purgeMethodAnalyses(MethodDescriptor methodDescriptor) { try { ClassContext classContext = getClassAnalysis(ClassContext.class, methodDescriptor.getClassDescriptor()); classContext.purgeMethodAnalyses(methodDescriptor); } catch (CheckedAnalysisException e) { IllegalStateException ise = new IllegalStateException("Unexpected exception purging method analyses from cache"); ise.initCause(e); throw ise; } } /** * Find or create a descriptor to analysis object map. * * @param <DescriptorType> type of descriptor used as the map's key type (ClassDescriptor or MethodDescriptor) * @param <E> type of analysis class * @param analysisClassToDescriptorMapMap analysis class to descriptor map map * @param engineMap analysis class to analysis engine map * @param analysisClass the analysis map * @return the descriptor to analysis object map */ private static <DescriptorType, E> Map<DescriptorType, Object> findOrCreateDescriptorMap(final Map<Class<?>, Map<DescriptorType, Object>> analysisClassToDescriptorMapMap, final Map<Class<?>, ? extends IAnalysisEngine<DescriptorType,E>> engineMap, final Class<E> analysisClass) { Map<DescriptorType, Object> descriptorMap = analysisClassToDescriptorMapMap.get(analysisClass); if (descriptorMap == null) { // Create a MapCache that allows the analysis engine to // decide that analysis results should be retained indefinitely. IAnalysisEngine<DescriptorType, E> engine = engineMap.get(analysisClass); if (analysisClass.equals(JavaClass.class)) { descriptorMap = new MapCache<DescriptorType, Object>(MAX_JAVACLASS_RESULTS_TO_CACHE); } else if (analysisClass.equals(ConstantPoolGen.class)) { descriptorMap = new MapCache<DescriptorType, Object>(MAX_CONSTANT_POOL_GEN_RESULTS_TO_CACHE); } else if (analysisClass.equals(ClassContext.class)) { descriptorMap = new MapCache<DescriptorType, Object>(10); } else if (engine instanceof IClassAnalysisEngine && ((IClassAnalysisEngine)engine).canRecompute()) { descriptorMap = new MapCache<DescriptorType, Object>(MAX_CLASS_RESULTS_TO_CACHE); } else { descriptorMap = new HashMap<DescriptorType, Object>(); } analysisClassToDescriptorMapMap.put(analysisClass, descriptorMap); } return descriptorMap; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#registerClassAnalysisEngine(java.lang.Class, edu.umd.cs.findbugs.classfile.IClassAnalysisEngine) */ public <E> void registerClassAnalysisEngine(Class<E> analysisResultType, IClassAnalysisEngine<E> classAnalysisEngine) { classAnalysisEngineMap.put(analysisResultType, classAnalysisEngine); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#registerMethodAnalysisEngine(java.lang.Class, edu.umd.cs.findbugs.classfile.IMethodAnalysisEngine) */ public <E> void registerMethodAnalysisEngine(Class<E> analysisResultType, IMethodAnalysisEngine<E> methodAnalysisEngine) { methodAnalysisEngineMap.put(analysisResultType, methodAnalysisEngine); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#registerDatabaseFactory(java.lang.Class, edu.umd.cs.findbugs.classfile.IDatabaseFactory) */ public <E> void registerDatabaseFactory(Class<E> databaseClass, IDatabaseFactory<E> databaseFactory) { databaseFactoryMap.put(databaseClass, databaseFactory); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getDatabase(java.lang.Class) */ public <E> E getDatabase(Class<E> databaseClass) { Object database = databaseMap.get(databaseClass); if (database == null) { try { // Find the database factory IDatabaseFactory<?> databaseFactory = databaseFactoryMap.get(databaseClass); if (databaseFactory == null) { throw new IllegalArgumentException( "No database factory registered for " + databaseClass.getName()); } // Create the database database = databaseFactory.createDatabase(); } catch (CheckedAnalysisException e) { // Error - record the analysis error database = new AbnormalAnalysisResult(e); } // FIXME: should catch and re-throw RuntimeExceptions? databaseMap.put(databaseClass, database); } if (database instanceof AbnormalAnalysisResult) { throw new UncheckedAnalysisException( "Error instantiating " + databaseClass.getName() + " database", ((AbnormalAnalysisResult)database).checkedAnalysisException); } return databaseClass.cast(database); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#eagerlyPutDatabase(java.lang.Class, java.lang.Object) */ public <E> void eagerlyPutDatabase(Class<E> databaseClass, E database) { databaseMap.put(databaseClass, database); } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getErrorLogger() */ public IErrorLogger getErrorLogger() { return bugReporter; } /* (non-Javadoc) * @see edu.umd.cs.findbugs.classfile.IAnalysisCache#getProfiler() */ public Profiler getProfiler() { return bugReporter.getProjectStats().getProfiler(); } }