/*
* 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();
}
}