package org.checkerframework.checker.nullness; /*>>> import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; */ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.nullness.KeyForPropagator.PropagationDirection; import org.checkerframework.checker.nullness.qual.Covariant; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.KeyForBottom; import org.checkerframework.checker.nullness.qual.PolyKeyFor; import org.checkerframework.checker.nullness.qual.UnknownKeyFor; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.framework.qual.PolyAll; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.DefaultTypeHierarchy; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.TypeHierarchy; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.framework.type.visitor.VisitHistory; import org.checkerframework.framework.util.AnnotationBuilder; import org.checkerframework.framework.util.GraphQualifierHierarchy; import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory; import org.checkerframework.framework.util.typeinference.TypeArgInferenceUtil; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TypesUtils; public class KeyForAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { protected final AnnotationMirror UNKNOWNKEYFOR, KEYFOR, KEYFORBOTTOM; private final KeyForPropagator keyForPropagator; public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker, true); KEYFOR = AnnotationUtils.fromClass(elements, KeyFor.class); UNKNOWNKEYFOR = AnnotationUtils.fromClass(elements, UnknownKeyFor.class); KEYFORBOTTOM = AnnotationUtils.fromClass(elements, KeyForBottom.class); keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR); // Add compatibility annotations: addAliasedAnnotation( org.checkerframework.checker.nullness.compatqual.KeyForDecl.class, KEYFOR); addAliasedAnnotation( org.checkerframework.checker.nullness.compatqual.KeyForType.class, KEYFOR); this.postInit(); } @Override protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { return new LinkedHashSet<Class<? extends Annotation>>( Arrays.asList( KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class, PolyAll.class)); } @Override public Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> constructorFromUse( NewClassTree tree) { Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> result = super.constructorFromUse(tree); final AnnotatedTypeMirror returnType = result.first.getReturnType(); // Can we square this with the KeyForPropagationTreeAnnotator Pair<Tree, AnnotatedTypeMirror> context = getVisitorState().getAssignmentContext(); if (returnType.getKind() == TypeKind.DECLARED && context != null && context.first != null) { AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(this, getPath(tree)); if (assignedTo != null) { // array types and boxed primitives etc don't require propagation if (assignedTo.getKind() == TypeKind.DECLARED) { final AnnotatedDeclaredType newClassType = (AnnotatedDeclaredType) returnType; keyForPropagator.propagate( newClassType, (AnnotatedDeclaredType) assignedTo, PropagationDirection.TO_SUBTYPE, this); } } } return result; } @Override protected TypeHierarchy createTypeHierarchy() { return new KeyForTypeHierarchy( checker, getQualifierHierarchy(), checker.getOption("ignoreRawTypeArguments", "true").equals("true"), checker.hasOption("invariantArrays")); } @Override protected TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator( super.createTreeAnnotator(), new KeyForPropagationTreeAnnotator(this, keyForPropagator)); } protected static class KeyForTypeHierarchy extends DefaultTypeHierarchy { public KeyForTypeHierarchy( BaseTypeChecker checker, QualifierHierarchy qualifierHierarchy, boolean ignoreRawTypes, boolean invariantArrayComponents) { super(checker, qualifierHierarchy, ignoreRawTypes, invariantArrayComponents); } @Override public boolean isSubtype( AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, VisitHistory visited) { //TODO: THIS IS FROM THE OLD TYPE HIERARCHY. WE SHOULD FIX DATA-FLOW/PROPAGATION TO DO THE RIGHT THING if (supertype.getKind() == TypeKind.TYPEVAR && subtype.getKind() == TypeKind.TYPEVAR) { // TODO: Investigate whether there is a nicer and more proper way to // get assignments between two type variables working. if (supertype.getAnnotations().isEmpty()) { return true; } } // Otherwise Covariant would cause trouble. if (subtype.hasAnnotation(KeyForBottom.class)) { return true; } return super.isSubtype(subtype, supertype, visited); } protected boolean isCovariant(final int typeArgIndex, final int[] covariantArgIndexes) { if (covariantArgIndexes != null) { for (int covariantIndex : covariantArgIndexes) { if (typeArgIndex == covariantIndex) { return true; } } } return false; } @Override public Boolean visitTypeArgs( AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, VisitHistory visited, boolean subtypeIsRaw, boolean supertypeIsRaw) { final boolean ignoreTypeArgs = ignoreRawTypes && (subtypeIsRaw || supertypeIsRaw); if (!ignoreTypeArgs) { //TODO: Make an option for honoring this annotation in DefaultTypeHierarchy? final TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement(); int[] covariantArgIndexes = null; if (supertypeElem.getAnnotation(Covariant.class) != null) { covariantArgIndexes = supertypeElem.getAnnotation(Covariant.class).value(); } final List<? extends AnnotatedTypeMirror> subtypeTypeArgs = subtype.getTypeArguments(); final List<? extends AnnotatedTypeMirror> supertypeTypeArgs = supertype.getTypeArguments(); if (subtypeTypeArgs.isEmpty() || supertypeTypeArgs.isEmpty()) { return true; } if (supertypeTypeArgs.size() > 0) { for (int i = 0; i < supertypeTypeArgs.size(); i++) { final AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); final AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); if (subtypeIsRaw || supertypeIsRaw) { rawnessComparer.isValidInHierarchy( subtype, supertype, currentTop, visited); } else { if (!isContainedBy( subTypeArg, superTypeArg, visited, isCovariant(i, covariantArgIndexes))) { return false; } } } } } return true; } } /* * Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values) */ public AnnotationMirror createKeyForAnnotationMirrorWithValue(LinkedHashSet<String> values) { // Create an AnnotationBuilder with the ArrayList AnnotationBuilder builder = new AnnotationBuilder(getProcessingEnv(), KeyFor.class); builder.setValue("value", values.toArray()); // Return the resulting AnnotationMirror return builder.build(); } /* * Given a string 'value', returns an AnnotationMirror corresponding to @KeyFor(value) */ public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) { // Create an ArrayList with the value LinkedHashSet<String> values = new LinkedHashSet<String>(); values.add(value); return createKeyForAnnotationMirrorWithValue(values); } /** * Returns true if the expression tree is a key for the map. * * @param mapExpression expression that has type Map * @param tree expression that might be a key for the map * @return whether or not the expression is a key for the map */ public boolean isKeyForMap(String mapExpression, ExpressionTree tree) { AnnotatedTypeMirror type = getAnnotatedType(tree); AnnotationMirror keyForAnno = type.getAnnotation(KeyFor.class); if (keyForAnno == null) { return false; } List<String> val = AnnotationUtils.getElementValueArray(keyForAnno, "value", String.class, false); return val.contains(mapExpression); } @Override public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { return new KeyForQualifierHierarchy(factory); } private final class KeyForQualifierHierarchy extends GraphQualifierHierarchy { public KeyForQualifierHierarchy(MultiGraphFactory factory) { super(factory, KEYFORBOTTOM); } @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (AnnotationUtils.areSameIgnoringValues(superAnno, KEYFOR) && AnnotationUtils.areSameIgnoringValues(subAnno, KEYFOR)) { List<String> lhsValues = null; List<String> rhsValues = null; Map<? extends ExecutableElement, ? extends AnnotationValue> valMap = superAnno.getElementValues(); if (valMap.isEmpty()) { lhsValues = new ArrayList<String>(); } else { lhsValues = AnnotationUtils.getElementValueArray( superAnno, "value", String.class, true); } valMap = subAnno.getElementValues(); if (valMap.isEmpty()) { rhsValues = new ArrayList<String>(); } else { rhsValues = AnnotationUtils.getElementValueArray( subAnno, "value", String.class, true); } return rhsValues.containsAll(lhsValues); } // Ignore annotation values to ensure that annotation is in supertype map. if (AnnotationUtils.areSameIgnoringValues(superAnno, KEYFOR)) { superAnno = KEYFOR; } if (AnnotationUtils.areSameIgnoringValues(subAnno, KEYFOR)) { subAnno = KEYFOR; } return super.isSubtype(subAnno, superAnno); } } private TypeMirror erasedMapType = null; protected boolean isInvocationOfMapMethod(MethodInvocationNode n, String methodName) { String invokedMethod = getMethodName(n); // First verify if the method name is correct. This is an inexpensive check. if (invokedMethod.equals(methodName)) { // Now verify that the receiver of the method invocation is of a type // that extends that java.util.Map interface. This is a more expensive check. if (erasedMapType == null) { TypeMirror mapType = TypesUtils.typeFromClass(types, elements, Map.class); erasedMapType = types.erasure(mapType); } TypeMirror receiverType = types.erasure(n.getTarget().getReceiver().getType()); if (types.isSubtype(receiverType, erasedMapType)) { return true; } } return false; } protected String getMethodName(MethodInvocationNode n) { String invokedMethod = n.getTarget().getMethod().toString(); int index = invokedMethod.indexOf("("); assert index != -1 : this.getClass() + ": expected method name to contain ("; invokedMethod = invokedMethod.substring(0, index); return invokedMethod; } }