All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.43.0
Show newest version
package org.checkerframework.framework.type.typeannotator;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultFor;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;

/**
 * Adds annotations to a type based on the use of a type. This class applies annotations specified
 * by {@link DefaultFor}; it is designed to be used in a {@link ListTypeAnnotator} constructed in
 * {@link GenericAnnotatedTypeFactory#createTypeAnnotator()} ()}
 *
 * 

{@link DefaultForTypeAnnotator} traverses types deeply. * *

This class takes care of two of the attributes of {@link DefaultFor}; the others are handled * in {@link org.checkerframework.framework.util.defaults.QualifierDefaults}. * * @see ListTypeAnnotator */ public class DefaultForTypeAnnotator extends TypeAnnotator { /** Map from {@link TypeKind} to annotations. */ private final Map typeKinds; /** Map from {@link AnnotatedTypeMirror} classes to annotations. */ private final Map, AnnotationMirrorSet> atmClasses; /** Map from fully qualified class name strings to annotations. */ private final Map types; /** * A list where each element associates an annotation with name regexes and name exception * regexes. */ private final ListOfNameRegexes listOfNameRegexes; /** {@link QualifierHierarchy} */ private final QualifierHierarchy qualHierarchy; /** * Creates a {@link DefaultForTypeAnnotator} from the given checker, using that checker to * determine the annotations that are in the type hierarchy. */ public DefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { super(typeFactory); this.typeKinds = new EnumMap<>(TypeKind.class); this.atmClasses = new HashMap<>(); this.types = new HashMap<>(); this.listOfNameRegexes = new ListOfNameRegexes(); this.qualHierarchy = typeFactory.getQualifierHierarchy(); // Get type qualifiers from the checker. Set> quals = typeFactory.getSupportedTypeQualifiers(); // For each qualifier, read the @DefaultFor annotation and put its types, kinds, and names // into maps. for (Class qual : quals) { DefaultFor defaultFor = qual.getAnnotation(DefaultFor.class); if (defaultFor == null) { continue; } AnnotationMirror theQual = AnnotationBuilder.fromClass(typeFactory.getElementUtils(), qual); for (org.checkerframework.framework.qual.TypeKind typeKind : defaultFor.typeKinds()) { TypeKind mappedTk = mapTypeKinds(typeKind); addTypeKind(mappedTk, theQual); } for (Class typeName : defaultFor.types()) { addTypes(typeName, theQual); } listOfNameRegexes.add(theQual, defaultFor); } } /** * Map between {@link org.checkerframework.framework.qual.TypeKind} and {@link * javax.lang.model.type.TypeKind}. * * @param typeKind the Checker Framework TypeKind * @return the javax TypeKind */ private TypeKind mapTypeKinds(org.checkerframework.framework.qual.TypeKind typeKind) { return TypeKind.valueOf(typeKind.name()); } /** Add default qualifier, {@code theQual}, for the given TypeKind. */ public void addTypeKind(TypeKind typeKind, AnnotationMirror theQual) { boolean res = qualHierarchy.updateMappingToMutableSet(typeKinds, typeKind, theQual); if (!res) { throw new BugInCF( "TypeAnnotator: invalid update of typeKinds " + typeKinds + " at " + typeKind + " with " + theQual); } } /** Add default qualifier, {@code theQual}, for the given {@link AnnotatedTypeMirror} class. */ public void addAtmClass( Class typeClass, AnnotationMirror theQual) { boolean res = qualHierarchy.updateMappingToMutableSet(atmClasses, typeClass, theQual); if (!res) { throw new BugInCF( "TypeAnnotator: invalid update of atmClasses " + atmClasses + " at " + typeClass + " with " + theQual); } } /** Add default qualifier, {@code theQual}, for the given type. */ public void addTypes(Class clazz, AnnotationMirror theQual) { String typeNameString = clazz.getCanonicalName(); boolean res = qualHierarchy.updateMappingToMutableSet(types, typeNameString, theQual); if (!res) { throw new BugInCF( "TypeAnnotator: invalid update of types " + types + " at " + clazz + " with " + theQual); } } @Override protected Void scan(AnnotatedTypeMirror type, Void p) { // If the type's fully-qualified name is in the appropriate map, annotate the type. Do this // before looking at kind or class, as this information is more specific. String qname; if (type.getKind() == TypeKind.DECLARED) { qname = TypesUtils.getQualifiedName((DeclaredType) type.getUnderlyingType()); } else if (type.getKind().isPrimitive()) { qname = type.getUnderlyingType().toString(); } else { qname = null; } if (qname != null) { AnnotationMirrorSet fromQname = types.get(qname); if (fromQname != null) { type.addMissingAnnotations(fromQname); } } // If the type's kind or class is in the appropriate map, annotate the type. AnnotationMirrorSet fromKind = typeKinds.get(type.getKind()); if (fromKind != null) { type.addMissingAnnotations(fromKind); } else if (!atmClasses.isEmpty()) { Class t = type.getClass(); AnnotationMirrorSet fromClass = atmClasses.get(t); if (fromClass != null) { type.addMissingAnnotations(fromClass); } } return super.scan(type, p); } /** * Adds standard rules. Currently, sets Void to bottom if no other qualifier is set for Void. * Also, see {@link LiteralTreeAnnotator#addStandardLiteralQualifiers()}. * * @return this */ public DefaultForTypeAnnotator addStandardDefaults() { if (!types.containsKey(Void.class.getCanonicalName())) { for (AnnotationMirror bottom : qualHierarchy.getBottomAnnotations()) { addTypes(Void.class, bottom); } } else { AnnotationMirrorSet annos = types.get(Void.class.getCanonicalName()); for (AnnotationMirror top : qualHierarchy.getTopAnnotations()) { if (qualHierarchy.findAnnotationInHierarchy(annos, top) == null) { addTypes(Void.class, qualHierarchy.getBottomAnnotation(top)); } } } return this; } /** * Apply defaults based on a variable name to a type. * * @param type a type to apply defaults to * @param name the name of the variable that has type {@code type}, or the name of the method * whose return type is {@code type} */ public void defaultTypeFromName(AnnotatedTypeMirror type, String name) { // TODO: Check whether the annotation is applicable to this Java type? AnnotationMirror defaultAnno = listOfNameRegexes.getDefaultAnno(name); if (defaultAnno != null) { if (atypeFactory .getQualifierHierarchy() .findAnnotationInHierarchy(type.getPrimaryAnnotations(), defaultAnno) == null) { type.addAnnotation(defaultAnno); } } } @Override public Void visitExecutable(AnnotatedExecutableType type, Void aVoid) { ExecutableElement element = type.getElement(); Iterator paramTypes = type.getParameterTypes().iterator(); for (VariableElement paramElt : element.getParameters()) { String paramName = paramElt.getSimpleName().toString(); AnnotatedTypeMirror paramType = paramTypes.next(); defaultTypeFromName(paramType, paramName); } String methodName = element.getSimpleName().toString(); AnnotatedTypeMirror returnType = type.getReturnType(); defaultTypeFromName(returnType, methodName); return super.visitExecutable(type, aVoid); } /** * A list where each element associates an annotation with name regexes and name exception * regexes. */ private static class ListOfNameRegexes extends ArrayList { static final long serialVersionUID = 20200218L; /** * Update this list from the {@code names} and {@code namesExceptions} fields of a @DefaultFor * annotation. * * @param theQual the qualifier that a @DefaultFor annotation is written on * @param defaultFor the @DefaultFor annotation written on {@code theQual} */ void add(AnnotationMirror theQual, DefaultFor defaultFor) { if (defaultFor.names().length != 0) { NameRegexes thisName = new NameRegexes(theQual); for (String nameRegex : defaultFor.names()) { try { thisName.names.add(Pattern.compile(nameRegex)); } catch (PatternSyntaxException e) { throw new TypeSystemError( "In annotation %s, names() value \"%s\" is not a regular expression", theQual, nameRegex); } } for (String namesExceptionsRegex : defaultFor.namesExceptions()) { try { thisName.namesExceptions.add(Pattern.compile(namesExceptionsRegex)); } catch (PatternSyntaxException e) { throw new TypeSystemError( "In annotation %s, namesExceptions() value \"%s\" is not a regular expression", theQual, namesExceptionsRegex); } } add(thisName); } else if (defaultFor.namesExceptions().length != 0) { throw new TypeSystemError( "On annotation %s, %s has empty names() but nonempty namesExceptions()", theQual, defaultFor); } } /** * Returns the annotation that should be the default for a variable of the given name, or for * the return type of a method of the given name. * * @param name a variable name * @return the annotation that should be the default for a variable named {@code name}, or null * if none */ @Nullable AnnotationMirror getDefaultAnno(String name) { if (this.isEmpty()) { return null; } AnnotationMirror result = null; for (NameRegexes nameRegexes : this) { if (nameRegexes.matches(name)) { if (result == null) { result = nameRegexes.anno; } else { // This could combine the annotatations instead, but I think doing so // silently would confuse users. throw new TypeSystemError( "Multiple annotations are applicable to the name \"%s\"", name); } } } return result; } } /** * Associates an annotation with the variable names that cause the annotation to be chosen as a * default. */ private static class NameRegexes { /** The annotation. */ final AnnotationMirror anno; /** The name regexes. */ final List names = new ArrayList<>(0); /** The name exception regexes. */ final List namesExceptions = new ArrayList<>(0); /** * Constructs a NameRegexes from a @DefaultFor annotation. * * @param theQual the qualifier that {@code defaultFor} is written on */ NameRegexes(AnnotationMirror theQual) { this.anno = theQual; } /** * Returns true if the regular expressions match the given name -- that is, if {@link #anno} * should be used as the default type for a variable named {@code name}, or for the return type * of a method named {@code name}. * * @param name a variable or method name * @return true if {@link #anno} should be used as the default for a variable named {@code name} */ public boolean matches(String name) { return names.stream().anyMatch(p -> p.matcher(name).matches()) && namesExceptions.stream().noneMatch(p -> p.matcher(name).matches()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy