package checkers.basetype; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.*; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Tree; import checkers.igj.quals.*; import checkers.quals.PolymorphicQualifier; import checkers.quals.SubtypeOf; import checkers.quals.TypeQualifiers; import checkers.quals.Unqualified; import checkers.source.SourceChecker; import checkers.types.*; import checkers.types.AnnotatedTypeMirror.AnnotatedDeclaredType; import checkers.util.*; import javax.lang.model.element.AnnotationMirror; import javax.annotation.processing.*; /** * An abstract {@link SourceChecker} that provides a simple {@link * checkers.source.SourceVisitor} implementation for typical assignment and * pseudo-assignment checking of annotated types. Pseudo-assignment checks * include method overriding checks, parameter passing, and method invocation. * * Most type-checker plug-ins will want to extend this class, instead of * {@link SourceChecker}. Checkers which require annotated types but not * subtype checking (e.g. for testing purposes) * should extend {@link SourceChecker}. * * Non-type checkers (e.g. checkers to enforce coding * styles) should extend {@link SourceChecker} or {@link AbstractProcessor} * directly; the Checker Framework is not designed for such checkers. * * <p> * * It is a convention that, for a type system Foo, the checker, the visitor, * and the annotated type factory are named as <i>FooChecker</i>, * <i>FooVisitor</i>, and <i>FooAnnotatedTypeFactory</i>. Some factory * methods uses this convention to construct the appriopriate classes * reflectively. * * <p> * * {@code BaseTypeChecker} encapsulates a group for factories for various * representations/classes related the type system, mainly: * <ul> * <li> {@link QualifierHierarchy}: * to represent the supported qualifiers in addition to their hierarchy, * mainly, subtyping rules</li> * <li> {@link TypeHierarchy}: * to check subtyping rules between <b>annotated types</b> rather than qualifiers</li> * <li> {@link AnnotatedTypeFactory}: * to construct qualified types enriched with implicit qualifiers * according to the type system rules</li> * <li> {@link BaseTypeVisitor}: * to visit the compiled Java files and check for violations of the type * system rules</li> * </ul> * * <p> * * Subclasses must specify the set of type qualifiers they support either by * annotating the subclass with {@link TypeQualifiers} or by overriding the * {@link #getSupportedTypeQualifiers()} method. * * <p> * * If the specified type qualifiers are meta-annotated with {@link SubtypeOf}, * this implementation will automatically construct the type qualifier * hierarchy. Otherwise, or if this behavior must be overridden, the subclass * may override the {@link #createQualifierHierarchy()} method. * * @see checkers.quals */ public abstract class BaseTypeChecker extends SourceChecker { /** To cache the supported type qualifiers */ private Set<Class<? extends Annotation>> supportedQuals; /** To represent the supported qualifiers and their hierarchy */ private QualifierHierarchy qualHierarchy; /** To compare annotated types with respect to qualHierarchy */ private TypeHierarchy typeHierarchy; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.supportedQuals = this.createSupportedTypeQualifiers(); this.qualHierarchy = this.createQualifierHierarchy(); this.typeHierarchy = this.createTypeHierarchy(); } // ********************************************************************** // Factory Methods, and corresponding getters: // The getter methods are separated from the creation methods to simplify // caching for subclasses // ********************************************************************** /** * If the checker class is annotated with {@link * TypeQualifiers}, return an immutable set with the same set * of classes as the annotation. If the class is not so annotated, * return an empty set. * * Subclasses may override this method to return an immutable set * of their supported type qualifiers. * * @return the type qualifiers supported this processor, or an empty * set if none * * @see TypeQualifiers */ protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() { Class<?> classType = this.getClass(); TypeQualifiers typeQualifiersAnnotation = classType.getAnnotation(TypeQualifiers.class); if (typeQualifiersAnnotation == null) return Collections.emptySet(); Set<Class<? extends Annotation>> typeQualifiers = new HashSet<Class<? extends Annotation>>(); for (Class<? extends Annotation> qualifier : typeQualifiersAnnotation.value()) typeQualifiers.add(qualifier); return Collections.unmodifiableSet(typeQualifiers); } /** * Returns an immutable set of the type qualifiers supported by this * checker. * * @see #createSupportedTypeQualifiers() * * @return the type qualifiers supported this processor, or an empty * set if none */ public final Set<Class<? extends Annotation>> getSupportedTypeQualifiers() { if (supportedQuals == null) supportedQuals = createSupportedTypeQualifiers(); return supportedQuals; } /** * Returns the type qualifier hierarchy graph to be used by this processor. * * The implementation builds the type qualifier hierarchy for the * {@link #getSupportedTypeQualifiers()} using the * meta-annotations found in them. The current implementation returns an * instance of {@code GraphQualifierHierarchy}. * * Subclasses may override this method to express any relationships that * cannot be inferred using meta-annotations (e.g. due to lack of * meta-annotations). * * @return an annotation relation tree representing the supported qualifiers */ protected QualifierHierarchy createQualifierHierarchy() { AnnotationUtils annoFactory = AnnotationUtils.getInstance(env); GraphQualifierHierarchy.Factory factory= new GraphQualifierHierarchy.Factory(); for (Class<? extends Annotation> typeQualifier : getSupportedTypeQualifiers()) { if (typeQualifier.equals(Unqualified.class)) { factory.addQualifier(null); continue; } AnnotationMirror typeQualifierAnno = annoFactory.fromClass(typeQualifier); factory.addQualifier(typeQualifierAnno); if (typeQualifier.getAnnotation(SubtypeOf.class) == null) { // polymorphic qualifiers don't need to declared their supertypes if (typeQualifier.getAnnotation(PolymorphicQualifier.class) != null) continue; throw new AssertionError(typeQualifier + " does not specify its super qualifiers"); } Class<? extends Annotation>[] superQualifiers = typeQualifier.getAnnotation(SubtypeOf.class).value(); for (Class<? extends Annotation> superQualifier : superQualifiers) { AnnotationMirror superAnno = null; if (superQualifier != Unqualified.class) superAnno = annoFactory.fromClass(superQualifier); factory.addSubtype(typeQualifierAnno, superAnno); } } QualifierHierarchy hierarchy = factory.build(); if (hierarchy.getTypeQualifiers().size() < 2) { throw new IllegalStateException("Invalid qualifier hierarchy: hierarchy requires at least two annotations: " + hierarchy.getTypeQualifiers()); } return hierarchy; } /** * Returns the type qualifier hierarchy graph to be used by this processor. * * @see #createQualifierHierarchy() * * @return the {@link QualifierHierarchy} for this checker */ public final QualifierHierarchy getQualifierHierarchy() { if (qualHierarchy == null) qualHierarchy = createQualifierHierarchy(); return qualHierarchy; } /** * Creates the type subtyping checker using the current type qualifier * hierarchy. * * Subclasses may override this method to specify new type checking * rules beyond the typical java subtyping rules. * * @return the type relations class to check type subtyping */ protected TypeHierarchy createTypeHierarchy() { return new TypeHierarchy(getQualifierHierarchy()); } /** * Returns the appropriate visitor that type checks the compilation unit * according to the type system rules. * * This implementation uses the checker naming convention to create the * appropriate visitor. If no visitor is found, it returns an instance of * {@link BaseTypeVisitor}. It reflectively invokes the constructor that * accepts this checker and the compilation unit tree (in that order) * as arguments. * * Subclasses have to override this method to create the appropriate * visitor if they do not follow the checker naming convention. * * @param root the compilation unit currently being visited * @return the type-checking visitor */ @Override protected BaseTypeVisitor<?, ?> createSourceVisitor(CompilationUnitTree root) { // Try to reflectively load the visitor. Class<?> checkerClass = this.getClass(); while (checkerClass != BaseTypeChecker.class) { final String classToLoad = checkerClass.getName().replace("Checker", "Visitor") .replace("Subchecker", "Visitor"); BaseTypeVisitor<?, ?> result = invokeConstructorFor(classToLoad, new Class<?>[] { this.getClass(), CompilationUnitTree.class }, new Object[] { this, root }); if (result != null) return result; checkerClass = checkerClass.getSuperclass(); } // If a visitor couldn't be loaded reflectively, return the default. return new BaseTypeVisitor<Void, Void>(this, root); } /** * Constructs an instance of the appropriate type factory for the * implemented type system. * * The default implementation uses the checker naming convention to create * the appropriate type factory. If no factory is found, it returns * {@link BasicAnnotatedTypeFactory}. It reflectively invokes the * constructor that accepts this checker and compilation unit tree * (in that order) as arguments. * * Subclasses have to override this method to create the appropriate * visitor if they do not follow the checker naming convention. * * @param root the currently visited compilation unit * @return the appropriate type factory */ @Override public AnnotatedTypeFactory createFactory(CompilationUnitTree root) { // Try to reflectively load the type factory. Class<?> checkerClass = this.getClass(); while (checkerClass != BaseTypeChecker.class) { final String classToLoad = checkerClass.getName().replace("Checker", "AnnotatedTypeFactory") .replace("Subchecker", "AnnotatedTypeFactory"); AnnotatedTypeFactory result = invokeConstructorFor(classToLoad, new Class<?>[] { this.getClass(), CompilationUnitTree.class }, new Object[] { this, root }); if (result != null) return result; checkerClass = checkerClass.getSuperclass(); } return new BasicAnnotatedTypeFactory<BaseTypeChecker>(this, root); } // ********************************************************************** // Type Relationship queries // ********************************************************************** /** * Tests whether one annotated type is a subtype of another, with * respect to the annotations on these types. * * Subclasses may wish to ignore annotations that are not related to the * type qualifiers they check. * * This implementation follows the subtype rules specified in * {@link TypeHierarchy}. Its behavior is undefined for any annotations * not specified by either {@link TypeQualifiers} or the result of * {@link #getSupportedTypeQualifiers()}. * @param sub the child type * @param sup the parent type * * @return true iff {@code sub} is a subtype of {@code sup} */ // Should other classes simply depend on TypeHierarchy directly? public boolean isSubtype(AnnotatedTypeMirror sub, AnnotatedTypeMirror sup) { return typeHierarchy.isSubtype(sub, sup); } /** * Tests that the qualifiers present on the useType are valid qualifiers, * given the qualifiers on the declaration of the type, declarationType. * * <p> * * The check is shallow, as it does not descend into generic or array * types (i.e. only performing the validity check on the raw type or * outmost array dimension). {@link BaseTypeVisitor#validateTypeOf(Tree)} * would call this for each type argument or array dimention separately. * * <p> * * For instance, in the IGJ type system, a {@code @Mutable} is an invalid * qualifier for {@link String}, as {@link String} is declared as * {@code @Immutable String}. * * <p> * * In most cases, {@code useType} simply needs to be a subtype of * {@code declarationType}, but there are exceptions. In IGJ, a variable may be * declared {@code @ReadOnly String}, even though {@link String} is * {@code @Immutable String}; {@link ReadOnly} is not a subtype of * {@link Immutable}. * * @param declarationType the type of the class (TypeElement) * @param useType the use of the class (instance type) * @return if the useType is a valid use of elemType */ public boolean isValidUse(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType) { return isSubtype(useType.getErased(), declarationType.getErased()); } /** * Tests whether the variable accessed is an assignable variable or not, * given the current scope * * @param varType the annotated variable type * @param variable tree used to access the variable * @return true iff variable is assignable in the current scope */ public boolean isAssignable(AnnotatedTypeMirror varType, AnnotatedTypeMirror receiverType, Tree variable) { return true; } // ********************************************************************** // Misc. methods // ********************************************************************** /** * Specify 'flow' and 'cast' as supported lint options for all Type checkers */ @Override public Set<String> getSupportedLintOptions() { Set<String> lintSet = new HashSet<String>(super.getSupportedLintOptions()); lintSet.add("cast"); lintSet.add("cast:redundant"); lintSet.add("cast:unsafe"); return lintSet; } /** * Invokes the constructor belonging to the class * named by {@code name} having the given parameter types on the given * arguments. Returns {@code null} if the class cannot be found, or the * constructor does not exist or cannot be invoked on the given arguments. * * @param <T> the type to which the constructor belongs * @param name the name of the class to which the constructor belongs * @param paramTypes the types of the constructor's parameters * @param args the arguments on which to invoke the constructor * @return the result of the constructor invocation on {@code args}, or * null if the constructor does not exist or could not be invoked */ @SuppressWarnings("unchecked") private static <T> T invokeConstructorFor(String name, Class<?>[] paramTypes, Object[] args) { // Load the class. Class<T> cls = null; try { cls = (Class<T>) Class.forName(name); } catch (Exception e) { // no class is found, simply return null return null; } assert cls != null; // Invoke the constructor. try { Constructor<T> ctor = cls.getConstructor(paramTypes); return ctor.newInstance(args); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } // On failure, return null. return null; } }