edu.umd.cs.findbugs.ba.jsr305.TypeQualifierApplications Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of findbugs Show documentation
Show all versions of findbugs Show documentation
Findbugs: Because it's easy!
The newest version!
/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2008, 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.jsr305;
import java.lang.annotation.ElementType;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.CheckReturnValue;
import javax.annotation.meta.When;
import org.objectweb.asm.Type;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.InnerClassAccess;
import edu.umd.cs.findbugs.ba.InnerClassAccessMap;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XMethod;
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.analysis.AnnotatedObject;
import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
import edu.umd.cs.findbugs.classfile.analysis.EnumValue;
import edu.umd.cs.findbugs.util.DualKeyHashMap;
/**
* Figure out where and how type qualifier annotations are applied.
*
* @author William Pugh
* @author David Hovemeyer
*/
public class TypeQualifierApplications {
static final boolean DEBUG = SystemProperties.getBoolean("ctq.applications.debug");
static final String DEBUG_METHOD = SystemProperties.getProperty("ctq.applications.method");
static final boolean DEBUG_DEFAULT_ANNOTATION = SystemProperties.getBoolean("ctq.applications.default.debug");
/**
* Should exclusive type qualifiers be handled?
*/
static final boolean CHECK_EXCLUSIVE = true;// SystemProperties.getBoolean("ctq.applications.checkexclusive");
static final boolean CHECK_EXHAUSTIVE = true; // SystemProperties.getBoolean("ctq.applications.checkexhaustive");
static class Data {
/**
* Type qualifier annotations applied directly to
* methods/fields/classes/etc.
*/
private final Map> directObjectAnnotations = new HashMap>();
/** Type qualifier annotations applied directly to method parameters. */
private final HashMap>> directParameterAnnotations = new HashMap>>();
/**
* Map of TypeQualifierValues to maps containing, for each
* AnnotatedObject, the effective TypeQualifierAnnotation (if any) for
* that AnnotatedObject.
*/
private final Map, Map> effectiveObjectAnnotations = new HashMap, Map>();
/**
* Map of TypeQualifierValues to maps containing, for each
* XMethod/parameter, the effective TypeQualifierAnnotation (if any) for
* that XMethod/parameter.
*/
private final Map, DualKeyHashMap> effectiveParameterAnnotations = new HashMap, DualKeyHashMap>();
}
private static ThreadLocal instance = new ThreadLocal() {
@Override
protected Data initialValue() {
if (DEBUG) {
System.out.println("constructing TypeQualifierApplications.Data");
}
return new Data();
}
};
public static void clearInstance() {
instance.remove();
}
private static Map, DualKeyHashMap> getEffectiveParameterAnnotations() {
return instance.get().effectiveParameterAnnotations;
}
private static Map, Map> getEffectiveObjectAnnotations() {
return instance.get().effectiveObjectAnnotations;
}
private static HashMap>> getDirectParameterAnnotations() {
return instance.get().directParameterAnnotations;
}
private static Map> getDirectObjectAnnotations() {
return instance.get().directObjectAnnotations;
}
public static void updateAnnotations(AnnotatedObject object) {
// TODO: Be smarter. Can we do something other than clear everything?
clearInstance();
}
/**
* Callback interface to compute effective TypeQualifierAnnotation on an
* AnnotatedObject or method parameter.
*/
private interface ComputeEffectiveTypeQualifierAnnotation {
public TypeQualifierAnnotation compute(TypeQualifierValue tqv);
}
/**
* Get the direct annotations (if any) on given AnnotatedObject.
*
* @param m
* an AnnotatedObject
* @return Collection of AnnotationValues representing annotations directly
* applied to this AnnotatedObject
*/
private static Collection getDirectAnnotation(AnnotatedObject m) {
Collection result = getDirectObjectAnnotations().get(m);
if (result != null) {
return result;
}
if (m.getAnnotationDescriptors().isEmpty()) {
return Collections. emptyList();
}
result = TypeQualifierResolver.resolveTypeQualifiers(m.getAnnotations());
if (result.size() == 0) {
result = Collections. emptyList();
}
getDirectObjectAnnotations().put(m, result);
return result;
}
/**
* Get the direct annotations (if any) on given method parameter.
*
* @param m
* a method
* @param parameter
* a parameter (0 == first parameter)
* @return Collection of AnnotationValues representing annotations directly
* applied to this parameter
*/
private static Collection getDirectAnnotation(XMethod m, int parameter) {
HashMap>> directParameterAnnotations = getDirectParameterAnnotations();
Map> map = directParameterAnnotations.get(m);
if (map == null) {
int n = m.getNumParams();
if (m.isVarArgs())
{
n--; // ignore annotations on varargs parameters
}
map = new HashMap>(n + 2);
for (int i = 0; i < n; i++) {
Collection a = TypeQualifierResolver.resolveTypeQualifiers(m.getParameterAnnotations(i));
if (!a.isEmpty()) {
map.put(i, a);
}
}
if (map.isEmpty()) {
map = Collections.emptyMap();
}
directParameterAnnotations.put(m, map);
}
Collection result = map.get(parameter);
if (result != null) {
return result;
}
return Collections.emptyList();
}
/**
* Populate a Set of TypeQualifierAnnotations representing directly-applied
* type qualifier annotations on given method parameter.
*
* @param result
* Set of TypeQualifierAnnotations
* @param o
* a method
* @param parameter
* a parameter (0 == first parameter)
*/
public static void getDirectApplications(Set result, XMethod o, int parameter) {
Collection values = getDirectAnnotation(o, parameter);
for (AnnotationValue v : values) {
constructTypeQualifierAnnotation(result, v);
}
}
/**
* Populate a Set of TypeQualifierAnnotations representing directly-applied
* type qualifier annotations on given AnnotatedObject.
*
* @param result
* Set of TypeQualifierAnnotations
* @param o
* an AnnotatedObject
* @param e
* ElementType representing kind of annotated object
*/
public static void getDirectApplications(Set result, AnnotatedObject o, ElementType e) {
if (!o.getElementType().equals(e)) {
return;
}
Collection values = getDirectAnnotation(o);
for (AnnotationValue v : values) {
constructTypeQualifierAnnotation(result, v);
}
}
/**
* Resolve a raw AnnotationValue into a TypeQualifierAnnotation.
*
* @param v
* a raw AnnotationValue
* @return a constructed TypeQualifierAnnotation
*/
public static TypeQualifierAnnotation constructTypeQualifierAnnotation(AnnotationValue v) {
assert v != null;
EnumValue whenValue = (EnumValue) v.getValue("when");
When when = whenValue == null ? When.ALWAYS : When.valueOf(whenValue.value);
ClassDescriptor annotationClass = v.getAnnotationClass();
TypeQualifierValue tqv = TypeQualifierValue.getValue(annotationClass, v.getValue("value"));
TypeQualifierAnnotation tqa = TypeQualifierAnnotation.getValue(tqv, when);
return tqa;
}
/**
* Resolve a raw AnnotationValue into a TypeQualifierAnnotation, storing
* result in given Set.
*
* @param set
* Set of resolved TypeQualifierAnnotations
* @param v
* a raw AnnotationValue
*/
public static void constructTypeQualifierAnnotation(Set set, AnnotationValue v) {
assert set != null;
TypeQualifierAnnotation tqa = constructTypeQualifierAnnotation(v);
set.add(tqa);
}
/**
* Populate Set of TypeQualifierAnnotations for given AnnotatedObject,
* taking into account annotations applied to outer scopes (e.g., enclosing
* classes and packages.)
*
* @param result
* Set of TypeQualifierAnnotations
* @param o
* an AnnotatedObject
* @param e
* ElementType representing kind of AnnotatedObject
*/
private static void getApplicableScopedApplications(Set result, AnnotatedObject o, ElementType e) {
if (!o.isSynthetic()) {
AnnotatedObject outer = o.getContainingScope();
if (outer != null) {
getApplicableScopedApplications(result, outer, e);
}
}
getDirectApplications(result, o, e);
}
/**
* Get the collection of resolved TypeQualifierAnnotations for a given
* AnnotatedObject, taking into account annotations applied to outer scopes
* (e.g., enclosing classes and packages.)
*
* @param o
* an AnnotatedObject
* @param e
* ElementType representing kind of AnnotatedObject
* @return Collection of resolved TypeQualifierAnnotations
*/
private static Collection getApplicableScopedApplications(AnnotatedObject o, ElementType e) {
Set result = new HashSet();
getApplicableScopedApplications(result, o, e);
return result;
}
/**
* Get the collection of resolved TypeQualifierAnnotations for a given
* parameter, taking into account annotations applied to outer scopes (e.g.,
* enclosing classes and packages.)
*
* @param o
* a method
* @param parameter
* a parameter (0 == first parameter)
* @return Collection of resolved TypeQualifierAnnotations
*/
private static Collection getApplicableScopedApplications(XMethod o, int parameter) {
Set result = new HashSet();
ElementType e = ElementType.PARAMETER;
getApplicableScopedApplications(result, o, e);
getDirectApplications(result, o, parameter);
return result;
}
/**
* Get the Collection of resolved TypeQualifierAnnotations representing
* directly applied and default (outer scope) type qualifier annotations for
* given AnnotatedObject.
*
*
* NOTE: does not properly account for inherited annotations on instance
* methods. It is ok to call this method to find out generally-relevant
* TypeQualifierAnnotations, but not to find the effective
* TypeQualifierAnnotation.
*
*
* @param o
* an AnnotatedObject
* @return Collection of TypeQualifierAnnotations applicable to the
* AnnotatedObject
*/
public static Collection getApplicableApplications(AnnotatedObject o) {
return getApplicableScopedApplications(o, o.getElementType());
}
/**
* Get the Collection of resolved TypeQualifierAnnotations representing
* directly applied and default (outer scope) type qualifier annotations for
* given method parameter.
*
*
* NOTE: does not properly account for inherited annotations on instance
* method parameters. It is ok to call this method to find out
* generally-relevant TypeQualifierAnnotations, but not to find the
* effective TypeQualifierAnnotation.
*
*
* @param o
* a method
* @param parameter
* a parameter (0 == first parameter)
* @return Collection of TypeQualifierAnnotations applicable to the method
* parameter
*/
public static Collection getApplicableApplications(XMethod o, int parameter) {
return getApplicableScopedApplications(o, parameter);
}
/**
* Look up a TypeQualifierAnnotation matching given TypeQualifierValue.
*
* @param typeQualifierAnnotations
* a Collection of TypeQualifierAnnotations
* @param typeQualifierValue
* a TypeQualifierValue
* @return matching TypeQualifierAnnotation, or null if none
*/
private static @CheckForNull
TypeQualifierAnnotation findMatchingTypeQualifierAnnotation(Collection typeQualifierAnnotations,
TypeQualifierValue typeQualifierValue) {
for (TypeQualifierAnnotation typeQualifierAnnotation : typeQualifierAnnotations) {
if (typeQualifierAnnotation.typeQualifier.equals(typeQualifierValue)) {
return typeQualifierAnnotation;
}
}
return null;
}
/**
* Look for a default type qualifier annotation.
*
* @param o
* an AnnotatedObject
* @param typeQualifierValue
* a TypeQualifierValue
* @param elementType
* type of element for which we're looking for a default
* annotation
* @return default TypeQualifierAnnotation, or null if none
*/
private static @CheckForNull
TypeQualifierAnnotation getDefaultAnnotation(AnnotatedObject o, TypeQualifierValue typeQualifierValue, ElementType elementType) {
//
// Try to find a default annotation using the standard JSR-305
// default annotation mechanism.
//
TypeQualifierAnnotation result;
Collection values = TypeQualifierResolver.resolveTypeQualifierDefaults(o.getAnnotations(), elementType);
TypeQualifierAnnotation tqa = extractAnnotation(values, typeQualifierValue);
if (tqa != null) {
// System.out.println("Found default annotation of " + tqa +
// " for element " + elementType + " in " + o);
return tqa;
}
//
// Try one of the FindBugs-specific default annotation mechanisms.
//
if ((result = checkFindBugsDefaultAnnotation(FindBugsDefaultAnnotations.DEFAULT_ANNOTATION, o, typeQualifierValue)) != null) {
return result;
}
switch (elementType) {
case FIELD:
result = checkFindBugsDefaultAnnotation(FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_FIELDS, o,
typeQualifierValue);
break;
case METHOD:
result = checkFindBugsDefaultAnnotation(FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_METHODS, o,
typeQualifierValue);
break;
case PARAMETER:
result = checkFindBugsDefaultAnnotation(FindBugsDefaultAnnotations.DEFAULT_ANNOTATION_FOR_PARAMETERS, o,
typeQualifierValue);
break;
default:
// ignore
break;
}
// Try out default JDT (Eclipse) annotations
if(result == null){
AnnotationValue annotationValue = o.getAnnotation(TypeQualifierResolver.eclipseNonNullByDefault);
if(annotationValue != null){
Collection resolvedTypeQualifiers = TypeQualifierResolver.resolveTypeQualifiers(annotationValue);
tqa = extractAnnotation(resolvedTypeQualifiers, typeQualifierValue);
if(tqa != null){
return tqa;
}
}
}
return result;
}
private static @CheckForNull
TypeQualifierAnnotation checkFindBugsDefaultAnnotation(ClassDescriptor defaultAnnotation, AnnotatedObject o,
TypeQualifierValue typeQualifierValue) {
if (DEBUG_DEFAULT_ANNOTATION) {
System.out.println("Checking for " + defaultAnnotation + " containing " + typeQualifierValue + " on " + o);
}
// - check to see if default annotation is present; if not, return null
AnnotationValue annotationValue = o.getAnnotation(defaultAnnotation);
if (annotationValue == null) {
if (DEBUG_DEFAULT_ANNOTATION) {
System.out.println(" ===> no " + defaultAnnotation);
}
return null;
}
// - get value - should be Type or array of Type
Object value = annotationValue.getValue("value");
if (value == null) {
if (DEBUG_DEFAULT_ANNOTATION) {
System.out.println(" ===> value is null");
}
return null;
}
Object[] types;
if (value instanceof Object[]) {
types = (Object[]) value;
} else {
types = new Object[] { value };
}
// - scan through array elements; see if any match the
// TypeQualifierValue (including type qualifier nicknames)
for (Object obj : types) {
if (!(obj instanceof Type)) {
if (DEBUG_DEFAULT_ANNOTATION) {
System.out
.println("Found a non-Type value in value array of " + defaultAnnotation.toString() + " annotation");
}
continue;
}
Type type = (Type) obj;
if (DEBUG_DEFAULT_ANNOTATION) {
System.out.println(" ===> checking " + type.getDescriptor());
}
if (type.getDescriptor().startsWith("[")) {
continue;
}
ClassDescriptor typeDesc = DescriptorFactory.instance().getClassDescriptor(type.getInternalName());
// There is no general way to figure out whether a particular
// type is a type qualifier we're interested in without
// resolving it.
AnnotationValue annotation = new AnnotationValue(typeDesc);
Collection resolvedTypeQualifiers = TypeQualifierResolver.resolveTypeQualifiers(annotation);
TypeQualifierAnnotation tqa = extractAnnotation(resolvedTypeQualifiers, typeQualifierValue);
if (tqa != null) {
return tqa;
}
}
return null;
}
private static TypeQualifierAnnotation extractAnnotation(Collection resolvedTypeQualifiers,
TypeQualifierValue typeQualifierValue) {
for (AnnotationValue typeQualifier : resolvedTypeQualifiers) {
TypeQualifierAnnotation tqa = constructTypeQualifierAnnotation(typeQualifier);
if (tqa.typeQualifier.equals(typeQualifierValue)) {
if (DEBUG) {
System.out.println(" ===> Found match " + tqa);
}
return tqa;
}
}
return null;
}
/**
* Get the effective TypeQualifierAnnotation on given AnnotatedObject. Takes
* into account inherited and default (outer scope) annotations. Also takes
* exclusive qualifiers into account.
*
* @param o
* an AnnotatedObject
* @param typeQualifierValue
* a TypeQualifierValue specifying kind of annotation we want to
* look up
* @return the effective TypeQualifierAnnotation, or null if there is no
* effective TypeQualifierAnnotation on this AnnotatedObject
*/
public static TypeQualifierAnnotation getEffectiveTypeQualifierAnnotation(AnnotatedObject o,
TypeQualifierValue typeQualifierValue) {
if (o instanceof XMethod) {
XMethod m = (XMethod) o;
if (m.getName().startsWith("access$")) {
InnerClassAccessMap icam = AnalysisContext.currentAnalysisContext().getInnerClassAccessMap();
try {
InnerClassAccess ica = icam.getInnerClassAccess(m.getClassName(), m.getName());
if (ica != null && ica.isLoad()) {
o = ica.getField();
}
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
return null;
}
}
}
TypeQualifierAnnotation tqa = computeEffectiveTypeQualifierAnnotation(typeQualifierValue, o);
final AnnotatedObject o2 = o;
if (CHECK_EXCLUSIVE && tqa == null && typeQualifierValue.isExclusiveQualifier()) {
tqa = computeExclusiveQualifier(typeQualifierValue, new ComputeEffectiveTypeQualifierAnnotation() {
@Override
public TypeQualifierAnnotation compute(TypeQualifierValue tqv) {
return computeEffectiveTypeQualifierAnnotation(tqv, o2);
}
@Override
public String toString() {
return o2.toString();
}
});
}
return tqa;
}
private static TypeQualifierAnnotation computeEffectiveTypeQualifierAnnotation(TypeQualifierValue typeQualifierValue,
AnnotatedObject o) {
Map map = getEffectiveObjectAnnotations().get(typeQualifierValue);
if (map == null) {
map = new HashMap();
getEffectiveObjectAnnotations().put(typeQualifierValue, map);
}
// Check cached answer
TypeQualifierAnnotation result;
if (map.containsKey(o)) {
result = map.get(o);
} else {
if (DEBUG) {
System.out.println("Looking up application of " + typeQualifierValue + " on " + o);
}
// Compute answer
TypeQualifierAnnotation tqa;
// See if there is a direct application
tqa = getDirectTypeQualifierAnnotation(o, typeQualifierValue);
// If it's an instance method, check for an inherited annotation
if (tqa == null && (o instanceof XMethod) && !((XMethod) o).isStatic() && !((XMethod) o).isPrivate()
&& !"".equals(((XMethod) o).getName())) {
tqa = getInheritedTypeQualifierAnnotation((XMethod) o, typeQualifierValue);
}
boolean methodOverrides = false;
if (tqa == TypeQualifierAnnotation.OVERRIDES_BUT_NO_ANNOTATION) {
methodOverrides = true;
tqa = null;
}
// Check for a default (outer scope) annotation
if (tqa == null) {
tqa = getDefaultTypeQualifierAnnotation(o, typeQualifierValue, methodOverrides);
}
// Cache computed answer
result = tqa;
map.put(o, result);
if (DEBUG && result != null) {
System.out.println(" => Answer: " + result.when + " on " + o);
}
}
// Return cached answer
return result;
}
/**
* Get a directly-applied TypeQualifierAnnotation on given AnnotatedObject.
*
* @param o
* an AnnotatedObject
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return directly-applied TypeQualifierAnnotation, or null if there is no
* such annotation on the AnnotatedObject
*/
private static TypeQualifierAnnotation getDirectTypeQualifierAnnotation(AnnotatedObject o,
TypeQualifierValue typeQualifierValue) {
TypeQualifierAnnotation result;
Set applications = new HashSet();
getDirectApplications(applications, o, o.getElementType());
result = findMatchingTypeQualifierAnnotation(applications, typeQualifierValue);
return result;
}
/**
* Get the effective inherited TypeQualifierAnnotation on given instance
* method.
*
* @param o
* an XMethod
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return effective TypeQualifierAnnotation inherited from overridden
* supertype methods, or null if there is no inherited
* TypeQualifierAnnotation
*/
public static TypeQualifierAnnotation getInheritedTypeQualifierAnnotation(XMethod o, TypeQualifierValue typeQualifierValue) {
assert !o.isStatic();
ReturnTypeAnnotationAccumulator accumulator = new ReturnTypeAnnotationAccumulator(typeQualifierValue, o);
try {
AnalysisContext.currentAnalysisContext().getSubtypes2().traverseSupertypesDepthFirst(o.getClassDescriptor(), accumulator);
TypeQualifierAnnotation result = accumulator.getResult().getEffectiveTypeQualifierAnnotation();
if (result == null && accumulator.overrides()) {
return TypeQualifierAnnotation.OVERRIDES_BUT_NO_ANNOTATION;
}
return result;
} catch (ClassNotFoundException e) {
AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e);
return null;
}
}
/**
* Get the default (outer scope) annotation applicable to given
* AnnotatedObject.
*
* @param o
* an AnnotatedObject
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return the applicable default TypeQualifierAnnotation, or null if there
* is no default TypeQualifierAnnotation
*/
private static TypeQualifierAnnotation getDefaultTypeQualifierAnnotation(AnnotatedObject o,
TypeQualifierValue typeQualifierValue, boolean stopAtClassScope) {
if (o.isSynthetic())
{
return null; // synthetic objects don't get default annotations
}
ElementType elementType = o.getElementType();
while (true) {
o = o.getContainingScope();
if (o == null) {
return null;
}
if (stopAtClassScope && o instanceof XClass) {
return null;
}
TypeQualifierAnnotation result;
// Check direct applications of the type qualifier
Set applications = new HashSet();
getDirectApplications(applications, o, elementType);
result = findMatchingTypeQualifierAnnotation(applications, typeQualifierValue);
if (result != null) {
// Great - found an outer scope with a relevant annotation
assert false : "I don't think we should be looking here";
return result;
}
// Check default annotations
result = getDefaultAnnotation(o, typeQualifierValue, elementType);
if (result != null) {
return result;
}
}
}
/**
* Get the effective TypeQualifierAnnotation on given method parameter.
* Takes into account inherited and default (outer scope) annotations. Also
* takes exclusive qualifiers into account.
*
* @param xmethod
* a method
* @param parameter
* a parameter (0 == first parameter)
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return effective TypeQualifierAnnotation on the parameter, or null if
* there is no effective TypeQualifierAnnotation
*/
public static @CheckForNull
TypeQualifierAnnotation getEffectiveTypeQualifierAnnotation(final XMethod xmethod, final int parameter,
TypeQualifierValue typeQualifierValue) {
TypeQualifierAnnotation tqa = computeEffectiveTypeQualifierAnnotation(typeQualifierValue, xmethod, parameter);
if (CHECK_EXCLUSIVE && tqa == null && typeQualifierValue.isExclusiveQualifier()) {
tqa = computeExclusiveQualifier(typeQualifierValue, new ComputeEffectiveTypeQualifierAnnotation() {
@Override
public TypeQualifierAnnotation compute(TypeQualifierValue tqv) {
return computeEffectiveTypeQualifierAnnotation(tqv, xmethod, parameter);
}
@Override
public String toString() {
return "parameter " + parameter + " of " + xmethod;
}
});
}
return tqa;
}
// static Map checked = new HashMap();
private static TypeQualifierAnnotation computeEffectiveTypeQualifierAnnotation(TypeQualifierValue typeQualifierValue,
XMethod xmethod, int parameter) {
if (DEBUG) {
// System.out.println("XX: "
// +System.identityHashCode(typeQualifierValue));
if (typeQualifierValue.value != null) {
System.out.println(" Value is " + typeQualifierValue.value + "("
+ typeQualifierValue.value.getClass().toString() + ")");
}
}
Map, DualKeyHashMap> effectiveParameterAnnotations = getEffectiveParameterAnnotations();
DualKeyHashMap map = effectiveParameterAnnotations.get(typeQualifierValue);
if (map == null) {
if (DEBUG) {
System.out.println("computeEffectiveTypeQualifierAnnotation: Creating map for " + typeQualifierValue);
}
map = new DualKeyHashMap();
effectiveParameterAnnotations.put(typeQualifierValue, map);
}
// Check cached answer
TypeQualifierAnnotation result;
if (map.containsKey(xmethod, parameter)) {
result = map.get(xmethod, parameter);
} else {
if (DEBUG) {
System.out.println("Looking up application of " + typeQualifierValue + " on " + xmethod + " parameter "
+ parameter);
}
// String desc =
// xmethod.toString()+":"+parameter+":"+typeQualifierValue;
// if (checked.containsKey(desc)) {
// //throw new IllegalStateException("Repeating computation of " +
// desc, checked.get(desc));
// System.out.println("Repeating computation of " + desc);
// System.out.println("Previously computed:");
// checked.get(desc).printStackTrace(System.out);
// throw new IllegalStateException();
// }
// checked.put(desc, new Throwable().fillInStackTrace());
// Compute answer
TypeQualifierAnnotation tqa;
if (xmethod.isVarArgs() && parameter == xmethod.getNumParams()-1) {
tqa = null;
if (DEBUG) {
System.out.print(" vararg parameters don't get type qualifiers");
}
}
else {
// Check direct application
if (DEBUG) {
System.out.print(" (1) Checking direct application...");
}
tqa = getDirectTypeQualifierAnnotation(xmethod, parameter, typeQualifierValue);
if (DEBUG) {
System.out.println(tqa != null ? "FOUND" : "none");
}
// If it's an instance method, check for inherited annotation
if (tqa == null && !xmethod.isStatic() && !xmethod.isPrivate() && !"".equals(xmethod.getName())) {
if (DEBUG) {
System.out.print(" (2) Checking inherited...");
}
tqa = getInheritedTypeQualifierAnnotation(xmethod, parameter, typeQualifierValue);
if (DEBUG) {
if (tqa == TypeQualifierAnnotation.OVERRIDES_BUT_NO_ANNOTATION) {
System.out.println("Overrides, no annotation inherited");
} else if (tqa != null) {
System.out.println("Inherited " + tqa.when);
} else {
System.out.println("Nothing inherited");
}
}
}
boolean overriddenMethod = false;
if (tqa == TypeQualifierAnnotation.OVERRIDES_BUT_NO_ANNOTATION) {
overriddenMethod = true;
tqa = null;
}
// Check for default (outer scope) annotation
if (tqa == null) {
if (xmethod.isVariableSynthetic((xmethod.isStatic() ? 0 : 1) + parameter)) {
if (DEBUG) {
System.out.print(" (3) Skipping default for synthetic parameter");
}
} else {
if (DEBUG) {
System.out.print(" (3) Checking default...");
}
tqa = getDefaultTypeQualifierAnnotationForParameters(xmethod, typeQualifierValue, overriddenMethod);
if (DEBUG) {
System.out.println(tqa != null ? "FOUND" : "none");
}
}
}
}
// Cache answer
result = tqa;
map.put(xmethod, parameter, result);
if (DEBUG) {
if (result == null) {
System.out.println(" => Answer: no annotation on parameter " + parameter + " of " + xmethod);
} else {
System.out.println(" => Answer: " + result.when + " on parameter " + parameter + " of " + xmethod);
}
}
}
if (!map.containsKey(xmethod, parameter)) {
throw new IllegalStateException("Did not populate cache?");
}
// Return cached answer
return result;
}
/**
* Get the TypeQualifierAnnotation directly applied to given method
* parameter.
*
* @param xmethod
* a method
* @param parameter
* a parameter (0 == first parameter)
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return TypeQualifierAnnotation directly applied to the parameter, or
* null if there is no directly applied TypeQualifierAnnotation
*/
public static @CheckForNull @CheckReturnValue
TypeQualifierAnnotation getDirectTypeQualifierAnnotation(XMethod xmethod, int parameter, TypeQualifierValue typeQualifierValue) {
XMethod bridge = xmethod.bridgeTo();
if (bridge != null) {
xmethod = bridge;
}
Set applications = new HashSet();
getDirectApplications(applications, xmethod, parameter);
if (DEBUG_METHOD != null && DEBUG_METHOD.equals(xmethod.getName())) {
System.out.println(" Direct applications are: " + applications);
}
return findMatchingTypeQualifierAnnotation(applications, typeQualifierValue);
}
/**
* Get the effective inherited TypeQualifierAnnotation on the given instance
* method parameter.
*
* @param xmethod
* an instance method
* @param parameter
* a parameter (0 == first parameter)
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @return effective inherited TypeQualifierAnnotation on the parameter, or
* null if there is not effective TypeQualifierAnnotation
*/
public static @CheckForNull
TypeQualifierAnnotation getInheritedTypeQualifierAnnotation(XMethod xmethod, int parameter,
TypeQualifierValue typeQualifierValue) {
assert !xmethod.isStatic();
ParameterAnnotationAccumulator accumulator = new ParameterAnnotationAccumulator(typeQualifierValue, xmethod, parameter);
try {
AnalysisContext.currentAnalysisContext().getSubtypes2().traverseSupertypesDepthFirst(xmethod.getClassDescriptor(), accumulator);
TypeQualifierAnnotation result = accumulator.getResult().getEffectiveTypeQualifierAnnotation();
if (result == null && accumulator.overrides()) {
return TypeQualifierAnnotation.OVERRIDES_BUT_NO_ANNOTATION;
}
return result;
} catch (ClassNotFoundException e) {
AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e);
return null;
}
}
/**
* Get the default (outer-scope) TypeQualifierAnnotation on given method
* parameter.
*
* @param xmethod
* a method
* @param typeQualifierValue
* the kind of TypeQualifierValue we are looking for
* @param stopAtMethodScope
* @return the default (outer scope) TypeQualifierAnnotation on the
* parameter, or null if there is no default TypeQualifierAnnotation
*/
private static @CheckForNull
TypeQualifierAnnotation getDefaultTypeQualifierAnnotationForParameters(XMethod xmethod,
TypeQualifierValue typeQualifierValue, boolean stopAtMethodScope) {
if (xmethod.isSynthetic())
{
return null; // synthetic methods don't get default annotations
}
// System.out.println("Looking for default " + typeQualifierValue +
// " annotation of parameters of " + xmethod);
if ("".equals(xmethod.getName()) && xmethod.getClassDescriptor().isAnonymousClass())
{
return null; // constructors for anonymous inner classes don't get
// default annotations
}
/** private methods don't inherit from class or package scope */
if (xmethod.isPrivate()) {
stopAtMethodScope = true;
}
boolean stopAtClassScope = false;
if (!xmethod.isPublic() && !xmethod.isProtected() && (xmethod.isStatic() || "".equals(xmethod.getName()))) {
try {
XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, xmethod.getClassDescriptor());
stopAtClassScope = xclass.isPrivate();
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Problem resolving class for " + xmethod, e);
}
}
AnnotatedObject o = xmethod;
while (true) {
if (o == null) {
return null;
}
if (stopAtMethodScope && o instanceof XClass) {
return null;
}
// Check for direct type qualifier annotation
Set applications = new HashSet();
getDirectApplications(applications, o, ElementType.PARAMETER);
TypeQualifierAnnotation tqa = findMatchingTypeQualifierAnnotation(applications, typeQualifierValue);
if (tqa != null) {
// Found matching annotation in outer scope
assert false : "I think this code is dead; it shouldn't find anything";
return tqa;
}
// Check for default annotation
tqa = getDefaultAnnotation(o, typeQualifierValue, ElementType.PARAMETER);
if (tqa != null) {
if (DEBUG) {
System.out.println("Found default of " + tqa + " @ " + o);
}
return tqa;
}
if (stopAtClassScope && o instanceof XClass) {
return null;
}
o = o.getContainingScope();
}
}
private static TypeQualifierAnnotation computeExclusiveQualifier(TypeQualifierValue typeQualifierValue,
ComputeEffectiveTypeQualifierAnnotation c) {
assert typeQualifierValue.isExclusiveQualifier();
boolean isExhaustive = CHECK_EXHAUSTIVE && typeQualifierValue.isExhaustiveQualifier();
// Exclusive qualifiers:
// - if there is an effective application of
// a "complementary" TypeQualifierValue in which
// when=ALWAYS. If so, then it's effectively
// the same as the asked-for TypeQualifierValue,
// but with when=NEVER.
//
// Exhaustive qualifiers:
// - if all effective applications of "complementary"
// TypeQualifierValues
// are when=NEVER, then the asked-for TypeQualifierValue
// is effectively when=ALWAYS.
boolean allComplementaryValuesAreWhenEqualsNever = true;
Collection> complementaryTypeQualifierValues = TypeQualifierValue
.getComplementaryExclusiveTypeQualifierValue(typeQualifierValue);
for (TypeQualifierValue complementaryTypeQualifierValue : complementaryTypeQualifierValues) {
TypeQualifierAnnotation complementaryTqa = c.compute(complementaryTypeQualifierValue);
if (complementaryTqa != null) {
if (complementaryTqa.when == When.ALWAYS) {
// Exclusive qualifier where a complementary qualifier
// was observed effectively when=ALWAYS.
return TypeQualifierAnnotation.getValue(typeQualifierValue, When.NEVER);
} else if (complementaryTqa.when != When.NEVER) {
allComplementaryValuesAreWhenEqualsNever = false;
}
} else {
allComplementaryValuesAreWhenEqualsNever = false;
}
}
if (isExhaustive && allComplementaryValuesAreWhenEqualsNever) {
// It's an exhaustive qualifier, and all complementary
// qualifiers were effectively when=NEVER.
if (TypeQualifierValue.DEBUG) {
System.out.println("*** application of " + typeQualifierValue + " on " + c + " is when=ALWAYS due to exhaustion");
}
return TypeQualifierAnnotation.getValue(typeQualifierValue, When.ALWAYS);
}
return null;
}
}