blob: c9b0cbed29ea56c62c94724b7e9f34b6ff0bb314 [file] [log] [blame]
package org.checkerframework.checker.nullness;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
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.checker.signature.qual.CanonicalName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.DefaultTypeHierarchy;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.SubtypeIsSupersetQualifierHierarchy;
import org.checkerframework.framework.type.TypeHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
public class KeyForAnnotatedTypeFactory
extends GenericAnnotatedTypeFactory<KeyForValue, KeyForStore, KeyForTransfer, KeyForAnalysis> {
/** The @{@link UnknownKeyFor} annotation. */
protected final AnnotationMirror UNKNOWNKEYFOR =
AnnotationBuilder.fromClass(elements, UnknownKeyFor.class);
/** The @{@link KeyForBottom} annotation. */
protected final AnnotationMirror KEYFORBOTTOM =
AnnotationBuilder.fromClass(elements, KeyForBottom.class);
/** The canonical name of the KeyFor class. */
protected final @CanonicalName String KEYFOR_NAME = KeyFor.class.getCanonicalName();
/** The Map.containsKey method. */
private final ExecutableElement mapContainsKey =
TreeUtils.getMethod("java.util.Map", "containsKey", 1, processingEnv);
/** The Map.get method. */
private final ExecutableElement mapGet =
TreeUtils.getMethod("java.util.Map", "get", 1, processingEnv);
/** The Map.put method. */
private final ExecutableElement mapPut =
TreeUtils.getMethod("java.util.Map", "put", 2, processingEnv);
/** The KeyFor.value field/element. */
protected final ExecutableElement keyForValueElement =
TreeUtils.getMethod(KeyFor.class, "value", 0, processingEnv);
/** Moves annotations from one side of a pseudo-assignment to the other. */
private final KeyForPropagator keyForPropagator = new KeyForPropagator(UNKNOWNKEYFOR);
/**
* If true, assume the argument to Map.get is always a key for the receiver map. This is set by
* the `-AassumeKeyFor` command-line argument. However, if the Nullness Checker is being run, then
* `-AassumeKeyFor` disables the Map Key Checker.
*/
private final boolean assumeKeyFor;
/**
* Creates a new KeyForAnnotatedTypeFactory.
*
* @param checker the associated checker
*/
public KeyForAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker, true);
assumeKeyFor = checker.hasOption("assumeKeyFor");
// Add compatibility annotations:
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.KeyForDecl", KeyFor.class, true);
addAliasedTypeAnnotation(
"org.checkerframework.checker.nullness.compatqual.KeyForType", KeyFor.class, true);
// While strictly required for soundness, this leads to too many false positives. Printing
// a key or putting it in a map erases all knowledge of what maps it was a key for.
// TODO: Revisit when side effect annotations are more precise.
// sideEffectsUnrefineAliases = true;
this.postInit();
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return new LinkedHashSet<>(
Arrays.asList(KeyFor.class, UnknownKeyFor.class, KeyForBottom.class, PolyKeyFor.class));
}
@Override
public ParameterizedExecutableType constructorFromUse(NewClassTree tree) {
ParameterizedExecutableType result = super.constructorFromUse(tree);
keyForPropagator.propagateNewClassTree(tree, result.executableType.getReturnType(), this);
return result;
}
@Override
protected TypeHierarchy createTypeHierarchy() {
return new KeyForTypeHierarchy(
checker,
getQualifierHierarchy(),
checker.getBooleanOption("ignoreRawTypeArguments", true),
checker.hasOption("invariantArrays"));
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(
super.createTreeAnnotator(), new KeyForPropagationTreeAnnotator(this, keyForPropagator));
}
// TODO: work on removing this class
protected static class KeyForTypeHierarchy extends DefaultTypeHierarchy {
public KeyForTypeHierarchy(
BaseTypeChecker checker,
QualifierHierarchy qualifierHierarchy,
boolean ignoreRawTypes,
boolean invariantArrayComponents) {
super(checker, qualifierHierarchy, ignoreRawTypes, invariantArrayComponents);
}
@Override
protected boolean isSubtype(
AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, AnnotationMirror top) {
// 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, top);
}
}
@Override
protected KeyForAnalysis createFlowAnalysis(
List<Pair<VariableElement, KeyForValue>> fieldValues) {
// Explicitly call the constructor instead of using reflection.
return new KeyForAnalysis(checker, this, fieldValues);
}
@Override
public KeyForTransfer createFlowTransferFunction(
CFAbstractAnalysis<KeyForValue, KeyForStore, KeyForTransfer> analysis) {
// Explicitly call the constructor instead of using reflection.
return new KeyForTransfer((KeyForAnalysis) analysis);
}
/**
* Given a string array 'values', returns an AnnotationMirror corresponding to @KeyFor(values)
*
* @param values the values for the {@code @KeyFor} annotation
* @return a {@code @KeyFor} annotation with the given values
*/
public AnnotationMirror createKeyForAnnotationMirrorWithValue(Set<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)
*
* @param value the argument to {@code @KeyFor}
* @return a {@code @KeyFor} annotation with the given value
*/
public AnnotationMirror createKeyForAnnotationMirrorWithValue(String value) {
return createKeyForAnnotationMirrorWithValue(Collections.singleton(value));
}
/**
* 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) {
// This test only has an effect if the Map Key Checker is being run on its own. If the Nullness
// Checker is being run, then -AassumeKeyFor disables the Map Key Checker.
if (assumeKeyFor) {
return true;
}
Collection<String> maps = null;
AnnotatedTypeMirror type = getAnnotatedType(tree);
AnnotationMirror keyForAnno = type.getAnnotation(KeyFor.class);
if (keyForAnno != null) {
maps = AnnotationUtils.getElementValueArray(keyForAnno, keyForValueElement, String.class);
} else {
KeyForValue value = getInferredValueFor(tree);
if (value != null) {
maps = value.getKeyForMaps();
}
}
return maps != null && maps.contains(mapExpression);
}
@Override
public QualifierHierarchy createQualifierHierarchy() {
return new SubtypeIsSupersetQualifierHierarchy(getSupportedTypeQualifiers(), processingEnv);
}
/** Returns true if the node is an invocation of Map.containsKey. */
boolean isMapContainsKey(Tree tree) {
return TreeUtils.isMethodInvocation(tree, mapContainsKey, getProcessingEnv());
}
/** Returns true if the node is an invocation of Map.get. */
boolean isMapGet(Tree tree) {
return TreeUtils.isMethodInvocation(tree, mapGet, getProcessingEnv());
}
/** Returns true if the node is an invocation of Map.put. */
boolean isMapPut(Tree tree) {
return TreeUtils.isMethodInvocation(tree, mapPut, getProcessingEnv());
}
/** Returns true if the node is an invocation of Map.containsKey. */
boolean isMapContainsKey(Node node) {
return NodeUtils.isMethodInvocation(node, mapContainsKey, getProcessingEnv());
}
/** Returns true if the node is an invocation of Map.get. */
boolean isMapGet(Node node) {
return NodeUtils.isMethodInvocation(node, mapGet, getProcessingEnv());
}
/** Returns true if the node is an invocation of Map.put. */
boolean isMapPut(Node node) {
return NodeUtils.isMethodInvocation(node, mapPut, getProcessingEnv());
}
/** Returns false. Redundancy in the KeyFor hierarchy is not worth warning about. */
@Override
public boolean shouldWarnIfStubRedundantWithBytecode() {
return false;
}
}