/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2003-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.ba.npe; import javax.annotation.meta.When; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.AnnotationDatabase; import edu.umd.cs.findbugs.ba.DefaultNullnessAnnotations; import edu.umd.cs.findbugs.ba.INullnessAnnotationDatabase; import edu.umd.cs.findbugs.ba.NullnessAnnotation; import edu.umd.cs.findbugs.ba.NullnessAnnotationDatabase; import edu.umd.cs.findbugs.ba.XClass; import edu.umd.cs.findbugs.ba.XFactory; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.XMethod; import edu.umd.cs.findbugs.ba.XMethodParameter; import edu.umd.cs.findbugs.ba.AnnotationDatabase.Target; import edu.umd.cs.findbugs.ba.jsr305.FindBugsDefaultAnnotations; import edu.umd.cs.findbugs.ba.jsr305.JSR305NullnessAnnotations; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierAnnotation; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications; import edu.umd.cs.findbugs.ba.jsr305.TypeQualifierValue; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.classfile.Global; import edu.umd.cs.findbugs.classfile.MissingClassException; import edu.umd.cs.findbugs.classfile.analysis.AnnotatedObject; import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue; import edu.umd.cs.findbugs.classfile.analysis.ClassInfo; import edu.umd.cs.findbugs.classfile.analysis.FieldInfo; import edu.umd.cs.findbugs.classfile.analysis.MethodInfo; import edu.umd.cs.findbugs.internalAnnotations.DottedClassName; import edu.umd.cs.findbugs.log.Profiler; /** * 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(); } }