blob: 9e04378e02bc92e1f318b69da0dabe1e3c087f4f [file] [log] [blame]
package org.checkerframework.framework.util.element;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Attribute.TypeCompound;
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeAnnotationPosition;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.ElementAnnotationApplier;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.StringsPlume;
/**
* Utility methods for adding the annotations that are stored in an Element to the type that
* represents that element (or a use of that Element). This class also contains package private
* methods used by the ElementAnnotationAppliers that do most of the work.
*/
public class ElementAnnotationUtil {
/**
* For each type/element pair, add all of the annotations stored in Element to type. See apply for
* more details.
*
* @param types the types to which we wish to apply element annotations
* @param elements the elements that may contain annotations to apply. elements.size must ==
* types.size
* @param typeFactory the type factory used to create the AnnotatedTypeMirrors contained by types
*/
public static void applyAllElementAnnotations(
final List<? extends AnnotatedTypeMirror> types,
final List<? extends Element> elements,
final AnnotatedTypeFactory typeFactory) {
if (types.size() != elements.size()) {
throw new BugInCF(
"Number of types and elements don't match. "
+ "types ( "
+ StringsPlume.join(", ", types)
+ " ) "
+ "element ( "
+ StringsPlume.join(", ", elements)
+ " ) ");
}
for (int i = 0; i < types.size(); i++) {
ElementAnnotationApplier.apply(types.get(i), elements.get(i), typeFactory);
}
}
/**
* When a declaration annotation is an alias for a type annotation, then the Checker Framework may
* move the annotation before replacing it by the canonical version.
*
* <p>If the annotation is one of the Checker Framework compatibility annotations, for example
* org.checkerframework.checker.nullness.compatqual.NonNullDecl, then it is interpreted as a type
* annotation in the same location.
*
* @param type the type to annotate
* @param annotations the annotations to add
*/
@SuppressWarnings("interning:not.interned") // AST node comparison
static void addDeclarationAnnotationsFromElement(
final AnnotatedTypeMirror type, final List<? extends AnnotationMirror> annotations) {
// The code here should be similar to
// org.checkerframework.framework.type.TypeFromMemberVisitor.visitVariable
AnnotatedTypeMirror innerType = AnnotatedTypes.innerMostType(type);
if (innerType != type) {
for (AnnotationMirror annotation : annotations) {
if (AnnotationUtils.annotationName(annotation).startsWith("org.checkerframework")) {
innerType.addAnnotation(annotation);
} else {
type.addAnnotation(annotation);
}
}
} else {
type.addAnnotations(annotations);
}
}
/**
* Does expectedValues contain enumValue. This is just a linear search.
*
* @param enumValue value to search for, a needle
* @param expectedValues values to search through, a haystack
* @return true if enumValue is in expectedValues, false otherwise
*/
static boolean contains(Object enumValue, Object[] expectedValues) {
for (final Object expected : expectedValues) {
if (enumValue.equals(expected)) {
return true;
}
}
return false;
}
/**
* Use a map to partition annotations with the given TargetTypes into Lists, where each target
* type is a key in the output map. Any annotation that does not have one of these target types
* will be added to unmatched
*
* @param annos the collection of annotations to partition
* @param unmatched a list to add annotations with unmatched target types to
* @param targetTypes a list of target types to partition annos with
* @return a map from targetType &rarr; List of Annotations that have that targetType
*/
static Map<TargetType, List<TypeCompound>> partitionByTargetType(
Collection<TypeCompound> annos, List<TypeCompound> unmatched, TargetType... targetTypes) {
final Map<TargetType, List<TypeCompound>> targetTypeToAnnos = new HashMap<>();
for (TargetType targetType : targetTypes) {
targetTypeToAnnos.put(targetType, new ArrayList<>());
}
for (final TypeCompound anno : annos) {
final List<TypeCompound> annoSet = targetTypeToAnnos.get(anno.getPosition().type);
if (annoSet != null) {
annoSet.add(anno);
} else if (unmatched != null) {
unmatched.add(anno);
}
}
return targetTypeToAnnos;
}
/**
* A class used solely to annotate wildcards from Element annotations. Instances of
* WildcardBoundAnnos are used to aggregate ALL annotations for a given Wildcard and then apply
* them all at once in order to resolve the annotations in front of unbound wildcards.
*
* <p>Wildcard annotations are applied as follows:
*
* <ul>
* <li>a) If an Annotation is in front of a extends or super bounded wildcard, it applies to the
* bound that is NOT explicitly present. e.g.
* <pre>{@code
* <@A ? extends Object> -- @A is placed on the super bound (Void)
* <@B ? super CharSequence> -- @B is placed on the extends bound (probably Object)
* }</pre>
* <li>b) If an Annotation is on a bound, it applies to that bound. E.g.
* <pre>{@code
* <? extends @A Object> -- @A is placed on the extends bound (Object)
* <? super @B CharSequence> -- @B is placed on the super bound (CharSequence)
* }</pre>
* <li>c) If an Annotation is on an unbounded wildcard there are two subcases.
* <ul>
* <li>c.1 The user wrote the annotation explicitly -- these annotations apply to both
* bounds e.g. the user wrote
* <pre>{@code
* <@C ?> -- the annotation is placed on the extends/super bounds
* }</pre>
* <li>c.2 Previous calls to getAnnotatedType have annotated this wildcard with BOTH
* bounds e.g. the user wrote {@code <?>} but the checker framework added {@code <@C ?
* extends @D Object>} to the corresponding element.
* <pre>
* {@code <?> -- @C is placed on the lower bound and @D is placed on the upper bound
* This case is treated just like annotations in cases a/b.
* }</pre>
* </ul>
* </ul>
*/
private static final class WildcardBoundAnnos {
public final AnnotatedWildcardType wildcard;
public final Set<AnnotationMirror> upperBoundAnnos;
public final Set<AnnotationMirror> lowerBoundAnnos;
// indicates that this is an annotation in front of an unbounded wildcard
// e.g. < @A ? >
// For each annotation in this set, if there is no annotation in upperBoundAnnos
// that is in the same hierarchy then the annotation will be applied to both bounds
// otherwise the annotation applies to the lower bound only
public final Set<AnnotationMirror> possiblyBoth;
/** Whether or not wildcard has an explicit super bound. */
private final boolean isSuperBounded;
/** Whether or not wildcard has NO explicit bound whatsoever. */
private final boolean isUnbounded;
WildcardBoundAnnos(AnnotatedWildcardType wildcard) {
this.wildcard = wildcard;
this.upperBoundAnnos = AnnotationUtils.createAnnotationSet();
this.lowerBoundAnnos = AnnotationUtils.createAnnotationSet();
this.possiblyBoth = AnnotationUtils.createAnnotationSet();
this.isSuperBounded = AnnotatedTypes.hasExplicitSuperBound(wildcard);
this.isUnbounded = AnnotatedTypes.hasNoExplicitBound(wildcard);
}
void addAnnotation(final TypeCompound anno) {
// if the typepath entry ends in Wildcard then the annotation should go on a bound
// otherwise, the annotation is in front of the wildcard
// e.g. @HERE ? extends Object
final boolean isInFrontOfWildcard =
anno.getPosition().location.last() != TypePathEntry.WILDCARD;
if (isInFrontOfWildcard && isUnbounded) {
possiblyBoth.add(anno);
} else {
// A TypePathEntry of WILDCARD indicates that it is placed on the bound
// use the type of the wildcard bound to determine which set to put it in
if (isInFrontOfWildcard) {
if (isSuperBounded) {
upperBoundAnnos.add(anno);
} else {
lowerBoundAnnos.add(anno);
}
} else { // it's on the bound
if (isSuperBounded) {
lowerBoundAnnos.add(anno);
} else {
upperBoundAnnos.add(anno);
}
}
}
}
/**
* Apply the annotations to wildcard according to the rules outlined in the comment at the
* beginning of this class.
*/
void apply() {
final AnnotatedTypeMirror extendsBound = wildcard.getExtendsBound();
final AnnotatedTypeMirror superBound = wildcard.getSuperBound();
for (AnnotationMirror extAnno : upperBoundAnnos) {
extendsBound.addAnnotation(extAnno);
}
for (AnnotationMirror supAnno : lowerBoundAnnos) {
superBound.addAnnotation(supAnno);
}
for (AnnotationMirror anno : possiblyBoth) {
superBound.addAnnotation(anno);
// This will be false if we've defaulted the bounds and are reading them again.
// In that case, we will have already created an annotation for the extends bound
// that should be honored and NOT overwritten.
if (!extendsBound.isAnnotatedInHierarchy(anno)) {
extendsBound.addAnnotation(anno);
}
}
}
}
/**
* TypeCompounds are implementations of AnnotationMirror that are stored on Elements. Each type
* compound has a TypeAnnotationPosition which identifies, relative to the "root" of a type, where
* an annotation should be placed. This method adds all of the given TypeCompounds to the correct
* location on type by interpreting the TypeAnnotationPosition.
*
* <p>Note: We handle all of the Element annotations on a type at once because we need to identify
* whether or not the element annotation in front of an unbound wildcard (e.g. {@code <@HERE ?>})
* should apply to only the super bound or both the super bound and the extends bound.
*
* @see org.checkerframework.framework.util.element.ElementAnnotationUtil.WildcardBoundAnnos
* @param type the type in which annos should be placed
* @param annos all of the element annotations, TypeCompounds, for type
*/
static void annotateViaTypeAnnoPosition(
final AnnotatedTypeMirror type, final Collection<TypeCompound> annos)
throws UnexpectedAnnotationLocationException {
final IdentityHashMap<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos =
new IdentityHashMap<>();
for (final TypeCompound anno : annos) {
AnnotatedTypeMirror target = getTypeAtLocation(type, anno.position.location, anno, false);
if (target.getKind() == TypeKind.WILDCARD) {
addWildcardToBoundMap((AnnotatedWildcardType) target, anno, wildcardToAnnos);
} else {
target.addAnnotation(anno);
}
}
for (WildcardBoundAnnos wildcardAnnos : wildcardToAnnos.values()) {
wildcardAnnos.apply();
}
}
/**
* Creates an entry in wildcardToAnnos for wildcard if one does not already exists. Adds anno to
* the WildcardBoundAnnos object for wildcard.
*/
private static void addWildcardToBoundMap(
final AnnotatedWildcardType wildcard,
final TypeCompound anno,
final Map<AnnotatedWildcardType, WildcardBoundAnnos> wildcardToAnnos) {
WildcardBoundAnnos boundAnnos =
wildcardToAnnos.computeIfAbsent(wildcard, WildcardBoundAnnos::new);
boundAnnos.addAnnotation(anno);
}
/**
* Returns true if the typeCompound is a primary annotation for the type it targets (or lower
* bound if this is a type variable or wildcard ). If you think of a type as a tree-like structure
* then a nested type any type that is not the root. E.g. {@code @T List< @N String>}, @T is on a
* top-level NON-nested type where as the annotation @N is on a nested type.
*
* @param typeCompound the type compound to inspect
* @return true if typeCompound is placed on a nested type, false otherwise
*/
static boolean isOnComponentType(final Attribute.TypeCompound typeCompound) {
return !typeCompound.position.location.isEmpty();
}
/**
* See the Type Annotation Specification on bounds
* (https://checkerframework.org/jsr308/specification/java-annotation-design.html).
*
* <p>TypeAnnotationPositions have bound indices when they represent an upper bound on a
* TypeVariable. The index 0 ALWAYS refers to the superclass type. If that supertype is implied to
* be Object (because we didn't specify an extends) then the actual types will be offset by 1
* (because index 0 is ALWAYS a class.
*
* <p>Therefore, These indices will be offset by -1 if the first type in the bound is an interface
* which implies the specified type itself is an interface.
*
* <p>Reminder: There will only be multiple bound types if the upperBound is an intersection.
*
* @param upperBoundTypes the list of upperBounds for the type with bound positions you wish to
* offset
* @return the bound offset for all TypeAnnotationPositions of TypeCompounds targeting these
* bounds
*/
static int getBoundIndexOffset(final List<? extends AnnotatedTypeMirror> upperBoundTypes) {
final int boundIndexOffset;
if (((Type) upperBoundTypes.get(0).getUnderlyingType()).isInterface()) {
boundIndexOffset = -1;
} else {
boundIndexOffset = 0;
}
return boundIndexOffset;
}
/**
* Overload of getTypeAtLocation with default values null/false for the annotation and array
* component flag, to make usage easier. Default visibility to allow usage within package.
*/
static AnnotatedTypeMirror getTypeAtLocation(
AnnotatedTypeMirror type, List<TypeAnnotationPosition.TypePathEntry> location)
throws UnexpectedAnnotationLocationException {
return getTypeAtLocation(type, location, null, false);
}
/**
* Given a TypePath into a type, return the component type that is located at the end of the
* TypePath.
*
* @param type a type containing the type specified by location
* @param location a type path into type
* @param anno an annotation to be applied to the inner types of a declared type if the declared
* type is itself a component type of an array
* @param isComponentTypeOfArray indicates whether the type under analysis is a component type of
* some array type
* @return the type specified by location
*/
private static AnnotatedTypeMirror getTypeAtLocation(
AnnotatedTypeMirror type,
List<TypeAnnotationPosition.TypePathEntry> location,
TypeCompound anno,
boolean isComponentTypeOfArray)
throws UnexpectedAnnotationLocationException {
if (location.isEmpty() && type.getKind() != TypeKind.DECLARED) {
// An annotation with an empty type path on a declared type applies to the outermost enclosing
// type. This logic is handled together with non-empty type paths in getLocationTypeADT. For
// other kinds of types, no work is required for an empty type path.
return type;
}
switch (type.getKind()) {
case NULL:
return getLocationTypeANT((AnnotatedNullType) type, location);
case DECLARED:
return getLocationTypeADT(
(AnnotatedDeclaredType) type, location, anno, isComponentTypeOfArray);
case WILDCARD:
return getLocationTypeAWT((AnnotatedWildcardType) type, location);
case TYPEVAR:
if (TypesUtils.isCaptured((TypeVariable) type.getUnderlyingType())) {
// Work-around for Issue 1696: ignore captured wildcards.
// There is no reason to observe such a type and it would be better
// to prevent that this type ever reaches this point.
return type;
}
// Raise an error for all other type variables (why isn't this needed?).
break;
case ARRAY:
return getLocationTypeAAT((AnnotatedArrayType) type, location, anno);
case UNION:
return getLocationTypeAUT((AnnotatedUnionType) type, location);
case INTERSECTION:
return getLocationTypeAIT((AnnotatedIntersectionType) type, location);
default:
// Raise an error for all other types below.
}
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.getTypeAtLocation: "
+ "unexpected annotation with location found for type: %s (kind: %s ) location: ",
type, type.getKind(), location);
}
/**
* Given a TypePath into a declared type, return the component type that is located at the end of
* the TypePath.
*
* @param type a type containing the type specified by location
* @param location a type path into type
* @param anno an annotation to be applied to the inner types of the declared type if the declared
* type is itself a component type of an array
* @param isComponentTypeOfArray indicates whether the type under analysis is a component type of
* some array type
* @return the type specified by location
*/
@SuppressWarnings("JdkObsolete") // error is issued on every operation, must suppress here
private static AnnotatedTypeMirror getLocationTypeADT(
AnnotatedDeclaredType type,
List<TypeAnnotationPosition.TypePathEntry> location,
TypeCompound anno,
boolean isComponentTypeOfArray)
throws UnexpectedAnnotationLocationException {
// List order by outermost type to innermost type.
ArrayDeque<AnnotatedDeclaredType> outerToInner = new ArrayDeque<>();
AnnotatedDeclaredType enclosing = type;
while (enclosing != null) {
outerToInner.addFirst(enclosing);
enclosing = enclosing.getEnclosingType();
}
// If the AnnotatedDeclaredType is a component of an array type, then apply anno to all
// possible inner types.
// NOTE: This workaround can be removed once
// https://bugs.openjdk.java.net/browse/JDK-8208470 is fixed
// The number of enclosing types is outerToInner.size() - 1; there only is
// work to do if outerToInner contains more than one element.
if (anno != null && isComponentTypeOfArray && location.isEmpty() && outerToInner.size() > 1) {
ArrayDeque<AnnotatedDeclaredType> innerTypes = new ArrayDeque<>(outerToInner);
innerTypes.removeFirst();
while (!innerTypes.isEmpty()) {
innerTypes.removeFirst().addAnnotation(anno);
}
}
// Create a linked list of the location, so removing the first element is easier.
// Also, the tail() operation wouldn't work with a Deque.
@SuppressWarnings("JdkObsolete")
LinkedList<TypePathEntry> tailOfLocations = new LinkedList<>(location);
boolean error = false;
while (!tailOfLocations.isEmpty()) {
TypePathEntry currentLocation = tailOfLocations.removeFirst();
switch (currentLocation.tag) {
case INNER_TYPE:
outerToInner.removeFirst();
break;
case TYPE_ARGUMENT:
AnnotatedDeclaredType innerType = outerToInner.getFirst();
if (currentLocation.arg < innerType.getTypeArguments().size()) {
AnnotatedTypeMirror typeArg = innerType.getTypeArguments().get(currentLocation.arg);
return getTypeAtLocation(typeArg, tailOfLocations);
} else {
error = true;
break;
}
default:
error = true;
}
if (error) {
break;
}
}
if (outerToInner.isEmpty() || error) {
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.getLocationTypeADT: invalid location %s for type: %s",
location, type);
}
return outerToInner.getFirst();
}
private static AnnotatedTypeMirror getLocationTypeANT(
AnnotatedNullType type, List<TypeAnnotationPosition.TypePathEntry> location)
throws UnexpectedAnnotationLocationException {
if (location.size() == 1 && location.get(0).tag == TypePathEntryKind.TYPE_ARGUMENT) {
return type;
}
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.getLocationTypeANT: " + "invalid location %s for type: %s ",
location, type);
}
private static AnnotatedTypeMirror getLocationTypeAWT(
final AnnotatedWildcardType type, final List<TypeAnnotationPosition.TypePathEntry> location)
throws UnexpectedAnnotationLocationException {
// the last step into the Wildcard type is handled in WildcardToBoundAnnos.addAnnotation
if (location.size() == 1) {
return type;
}
if (!location.isEmpty()
&& location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.WILDCARD) {
if (AnnotatedTypes.hasExplicitExtendsBound(type)) {
return getTypeAtLocation(type.getExtendsBound(), tail(location));
} else if (AnnotatedTypes.hasExplicitSuperBound(type)) {
return getTypeAtLocation(type.getSuperBound(), tail(location));
} else {
return getTypeAtLocation(type.getExtendsBound(), tail(location));
}
} else {
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.getLocationTypeAWT: " + "invalid location %s for type: %s ",
location, type);
}
}
/**
* When we have an (e.g. @Odd int @NonNull []) the type-annotation position of the array
* annotation (@NonNull) is really the outermost type in the TypeAnnotationPosition and it will
* NOT have TypePathEntryKind.ARRAY at the end of its position. The position of the component type
* (@Odd) is considered deeper in the type and therefore has the TypePathEntryKind.ARRAY in its
* position.
*/
private static AnnotatedTypeMirror getLocationTypeAAT(
AnnotatedArrayType type,
List<TypeAnnotationPosition.TypePathEntry> location,
TypeCompound anno)
throws UnexpectedAnnotationLocationException {
if (location.size() >= 1
&& location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.ARRAY) {
AnnotatedTypeMirror comptype = type.getComponentType();
return getTypeAtLocation(comptype, tail(location), anno, true);
} else {
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.annotateAAT: " + "invalid location %s for type: %s ",
location, type);
}
}
/*
* TODO: this case should never occur!
* A union type can only occur in special locations, e.g. for exception
* parameters. The EXCEPTION_PARAMETER TartetType should be used to
* decide which of the alternatives in the union to annotate.
* Only the TypePathEntry is not enough.
* As a hack, always annotate the first alternative.
*/
private static AnnotatedTypeMirror getLocationTypeAUT(
AnnotatedUnionType type, List<TypeAnnotationPosition.TypePathEntry> location)
throws UnexpectedAnnotationLocationException {
AnnotatedTypeMirror comptype = type.getAlternatives().get(0);
return getTypeAtLocation(comptype, location);
}
/** Intersection types use the TYPE_ARGUMENT index to separate the individual types. */
private static AnnotatedTypeMirror getLocationTypeAIT(
AnnotatedIntersectionType type, List<TypeAnnotationPosition.TypePathEntry> location)
throws UnexpectedAnnotationLocationException {
if (location.size() >= 1
&& location.get(0).tag == TypeAnnotationPosition.TypePathEntryKind.TYPE_ARGUMENT) {
AnnotatedTypeMirror bound = type.getBounds().get(location.get(0).arg);
return getTypeAtLocation(bound, tail(location));
} else {
throw new UnexpectedAnnotationLocationException(
"ElementAnnotationUtil.getLocatonTypeAIT: invalid location %s for type: %s ",
location, type);
}
}
private static <T> List<T> tail(List<T> list) {
return list.subList(1, list.size());
}
/** Exception indicating an invalid location for an annotation was found. */
@SuppressWarnings("serial")
public static class UnexpectedAnnotationLocationException extends Exception {
/**
* Creates an UnexpectedAnnotationLocationException.
*
* @param format format string
* @param args arguments to the format string
*/
@FormatMethod
private UnexpectedAnnotationLocationException(String format, Object... args) {
super(String.format(format, args));
}
}
/** An ERROR TypeKind was found. */
@SuppressWarnings("serial")
public static class ErrorTypeKindException extends Error {
/**
* Creates an ErrorTypeKindException.
*
* @param format format string
* @param args arguments to the format string
*/
@FormatMethod
public ErrorTypeKindException(String format, Object... args) {
super(String.format(format, args));
}
}
}