package org.checkerframework.common.basetype; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import org.checkerframework.common.reflection.MethodValChecker; import org.checkerframework.dataflow.cfg.CFGVisualizer; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.TypeHierarchy; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.InternalUtils; /** * An abstract {@link SourceChecker} that provides a simple {@link * org.checkerframework.framework.source.SourceVisitor} implementation that type-checks assignments, * pseudo-assignments such as parameter passing and method invocation, and method overriding. * * <p>Most type-checker annotation processor should extend this class, instead of {@link * SourceChecker}. Checkers that 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) can extend {@link SourceChecker} or {@link AbstractTypeProcessor}; 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 use this convention to construct the * appropriate 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>{@link TypeHierarchy}: to check subtyping rules between <b>annotated types</b> rather than * qualifiers * <li>{@link AnnotatedTypeFactory}: to construct qualified types enriched with implicit * qualifiers according to the type system rules * <li>{@link BaseTypeVisitor}: to visit the compiled Java files and check for violations of the * type system rules * </ul> * * <p>Subclasses must specify the set of type qualifiers they support. See {@link * AnnotatedTypeFactory#createSupportedTypeQualifiers()}. * * <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 * BaseAnnotatedTypeFactory#createQualifierHierarchy()} method. * * @see org.checkerframework.framework.qual * @checker_framework.manual #creating-compiler-interface The checker class */ public abstract class BaseTypeChecker extends SourceChecker implements BaseTypeContext { @Override public void initChecker() { // initialize all checkers and share options as necessary for (BaseTypeChecker checker : getSubcheckers()) { checker.initChecker(); // We need to add all options that are activated for the set of subcheckers to // the individual checkers. checker.addOptions(super.getOptions()); // Each checker should "support" all possible lint options - otherwise // subchecker A would complain about a lint option for subchecker B. checker.setSupportedLintOptions(this.getSupportedLintOptions()); } super.initChecker(); } /* * The full list of subcheckers that need to be run prior to this one, * in the order they need to be run in. This list will only be * non-empty for the one checker that runs all other subcheckers. Do * not read this field directly. Instead, retrieve it via {@link * #getSubcheckers}. * <p> * * If the list still null when {@link #getSubcheckers} is called, then * getSubcheckers() will call {@link #instantiateSubcheckers}. * However, if the current object was itself instantiated by a prior * call to instantiateSubcheckers, this field will have been * initialized to an empty list before getSubcheckers() is called, * thereby ensuring that this list is non-empty only for one checker. */ private List<BaseTypeChecker> subcheckers = null; /* * The list of subcheckers that are direct dependencies of this checker. * This list will be non-empty for any checker that has at least one subchecker. * * Does not need to be initialized to null or an empty list because it is always * initialized via calls to instantiateSubcheckers. */ private List<BaseTypeChecker> immediateSubcheckers; /** * Returns the set of subchecker classes on which this checker depends. Returns an empty set if * this checker does not depend on any others. * * <p>Subclasses should override this method to specify subcheckers. If they do so, they should * call the super implementation of this method and add dependencies to the returned set so that * checkers required for reflection resolution are included if reflection resolution is * requested. * * <p>Each subchecker of this checker may also depend on other checkers. If this checker and one * of its subcheckers both depend on a third checker, that checker will only be instantiated * once. * * <p>Though each checker is run on a whole compilation unit before the next checker is run, * error and warning messages are collected and sorted based on the location in the source file * before being printed. (See {@link #printMessage(Kind, String, Tree, CompilationUnitTree)}.) * * <p>WARNING: Circular dependencies are not supported nor do checkers verify that their * dependencies are not circular. Make sure no circular dependencies are created when overriding * this method. (In other words, if checker A depends on checker B, checker B cannot depend on * checker A.) * * <p>This method is protected so it can be overridden, but it should only be called internally * by the BaseTypeChecker. * * <p>The BaseTypeChecker will not modify the list returned by this method. */ protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() { if (shouldResolveReflection()) { return new LinkedHashSet<Class<? extends BaseTypeChecker>>( Collections.singleton(MethodValChecker.class)); } return new LinkedHashSet<Class<? extends BaseTypeChecker>>(); } /** Returns whether or not reflection should be resolved */ public boolean shouldResolveReflection() { // Because this method is indirectly called by getSubcheckers and // this.getOptions or this.hasOption // also call getSubcheckers, super.getOptions is called here. return super.getOptions().containsKey("resolveReflection"); } /** * Returns the appropriate visitor that type-checks the compilation unit according to the type * system rules. * * <p>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. * * <p>Subclasses have to override this method to create the appropriate visitor if they do not * follow the checker naming convention. * * @return the type-checking visitor */ @Override protected BaseTypeVisitor<?> createSourceVisitor() { // 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<?>[] {BaseTypeChecker.class}, new Object[] {this}); if (result != null) { return result; } checkerClass = checkerClass.getSuperclass(); } // If a visitor couldn't be loaded reflectively, return the default. return new BaseTypeVisitor<BaseAnnotatedTypeFactory>(this); } // ********************************************************************** // Misc. methods // ********************************************************************** /** Specify 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"); for (BaseTypeChecker checker : getSubcheckers()) { lintSet.addAll(checker.getSupportedLintOptions()); } return Collections.unmodifiableSet(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", "TypeParameterUnusedInFormals"}) // Intentional abuse public 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 : "reflectively loading " + name + " failed"; // Invoke the constructor. try { Constructor<T> ctor = cls.getConstructor(paramTypes); return ctor.newInstance(args); } catch (Throwable t) { if (t instanceof InvocationTargetException) { Throwable err = t.getCause(); String msg; if (err instanceof CheckerError) { CheckerError ce = (CheckerError) err; if (ce.userError) { // Don't add another stack frame, just show the message. throw ce; } else { msg = err.getMessage(); } } else { msg = err.toString(); } ErrorReporter.errorAbort( "InvocationTargetException when invoking constructor for class " + name + "; Underlying cause: " + msg, t); } else { ErrorReporter.errorAbort( "Unexpected " + t.getClass().getSimpleName() + " for " + "class " + name + " when invoking the constructor; parameter types: " + Arrays.toString(paramTypes), // + " and args: " + Arrays.toString(args), t); } return null; // dead code } } @Override public BaseTypeContext getContext() { return this; } @Override public BaseTypeChecker getChecker() { return this; } @Override public BaseTypeVisitor<?> getVisitor() { return (BaseTypeVisitor<?>) super.getVisitor(); } @Override public GenericAnnotatedTypeFactory<?, ?, ?, ?> getTypeFactory() { return getVisitor().getTypeFactory(); } @Override public AnnotationProvider getAnnotationProvider() { return getTypeFactory(); } /** * Returns the requested subchecker. A checker of a given class can only be run once, so this * returns the only such checker, or null if none was found. The caller must know the exact * checker class to request. * * @param checkerClass the class of the subchecker * @return the requested subchecker or null if not found */ @SuppressWarnings("unchecked") public <T extends BaseTypeChecker> T getSubchecker(Class<T> checkerClass) { for (BaseTypeChecker checker : immediateSubcheckers) { if (checker.getClass().equals(checkerClass)) { return (T) checker; } } return null; } /** * Returns the type factory used by a subchecker. Returns null if no matching subchecker was * found or if the type factory is null. The caller must know the exact checker class to * request. * * @param checkerClass the class of the subchecker * @return the type factory of the requested subchecker or null if not found */ @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse public <T extends GenericAnnotatedTypeFactory<?, ?, ?, ?>, U extends BaseTypeChecker> T getTypeFactoryOfSubchecker(Class<U> checkerClass) { BaseTypeChecker checker = getSubchecker(checkerClass); if (checker != null) { return (T) checker.getTypeFactory(); } return null; } /* * Performs a depth first search for all checkers this checker depends on. * The depth first search ensures that the collection has the correct order the checkers need to be run in. * * Modifies the alreadyInitializedSubcheckerMap map by adding all recursively newly instantiated subcheckers' class objects and instances. * A LinkedHashMap is used because, unlike HashMap, it preserves the order in which entries were inserted. * * Returns the unmodifiable list of immediate subcheckers of this checker. */ private List<BaseTypeChecker> instantiateSubcheckers( LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker> alreadyInitializedSubcheckerMap) { LinkedHashSet<Class<? extends BaseTypeChecker>> classesOfImmediateSubcheckers = getImmediateSubcheckerClasses(); ArrayList<BaseTypeChecker> immediateSubcheckers = new ArrayList<BaseTypeChecker>(); for (Class<? extends BaseTypeChecker> subcheckerClass : classesOfImmediateSubcheckers) { BaseTypeChecker subchecker = alreadyInitializedSubcheckerMap.get(subcheckerClass); if (subchecker != null) { // Add the already initialized subchecker to the list of immediate subcheckers so that this checker can refer to it. immediateSubcheckers.add(subchecker); continue; } try { BaseTypeChecker instance = subcheckerClass.getDeclaredConstructor().newInstance(); instance.setProcessingEnvironment(this.processingEnv); // Prevent the new checker from storing non-immediate subcheckers instance.subcheckers = Collections.unmodifiableList(new ArrayList<BaseTypeChecker>()); immediateSubcheckers.add(instance); instance.immediateSubcheckers = instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap); instance.setParentChecker(this); alreadyInitializedSubcheckerMap.put(subcheckerClass, instance); } catch (Exception e) { ErrorReporter.errorAbort("Could not create an instance of " + subcheckerClass); } } return Collections.unmodifiableList(immediateSubcheckers); } /* * Get the list of all subcheckers (if any). via the instantiateSubcheckers method. * This list is only non-empty for the one checker that runs all other subcheckers. * These are recursively instantiated via instantiateSubcheckers the first time * the method is called if subcheckers is null. * Assumes all checkers run on the same thread. */ private List<BaseTypeChecker> getSubcheckers() { if (subcheckers == null) { // Instantiate the checkers this one depends on, if any. LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker> checkerMap = new LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker>(); immediateSubcheckers = instantiateSubcheckers(checkerMap); subcheckers = Collections.unmodifiableList( new ArrayList<BaseTypeChecker>(checkerMap.values())); } return subcheckers; } /** * Sort by position at which the error will be printed, then by the order in which the checkers * run, then by kind of message, and finally by the message string. */ private final Comparator<CheckerMessage> checkerMessageComparator = new Comparator<CheckerMessage>() { @Override public int compare(CheckerMessage o1, CheckerMessage o2) { int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source); if (byPos != 0) { return byPos; } // Sort by order in which the checkers are run. (All the subcheckers in // followed by the checker.) int o1Index = BaseTypeChecker.this.getSubcheckers().indexOf(o1.checker); int o2Index = BaseTypeChecker.this.getSubcheckers().indexOf(o2.checker); if (o1Index != o2Index) { if (o1Index == -1) { o1Index = BaseTypeChecker.this.getSubcheckers().size(); } if (o2Index == -1) { o2Index = BaseTypeChecker.this.getSubcheckers().size(); } return Integer.compare(o1Index, o2Index); } int kind = o1.kind.compareTo(o2.kind); if (kind != 0) { return kind; } return o1.message.compareTo(o2.message); } }; // AbstractTypeProcessor delegation @Override public void typeProcess(TypeElement element, TreePath tree) { if (getSubcheckers().size() > 0) { messageStore = new TreeSet<>(checkerMessageComparator); } // Errors (or other messages) issued via // SourceChecker#message(Diagnostic.Kind, Object, String, Object...) // are stored in messageStore until all checkers have processed this compilation unit. // All other messages are printed immediately. This includes errors issued because the // checker threw an exception or called ErrorReporter.errorAbort(). // In order to run the next checker on this compilation unit even if the previous // issued errors, the next checker's errsOnLastExit needs to include all errors // issued by previous checkers. Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); Log log = Log.instance(context); int nerrorsOfAllPreviousCheckers = this.errsOnLastExit; for (BaseTypeChecker subchecker : getSubcheckers()) { subchecker.errsOnLastExit = nerrorsOfAllPreviousCheckers; subchecker.messageStore = messageStore; int errorsBeforeTypeChecking = log.nerrors; subchecker.typeProcess(element, tree); int errorsAfterTypeChecking = log.nerrors; nerrorsOfAllPreviousCheckers += errorsAfterTypeChecking - errorsBeforeTypeChecking; } this.errsOnLastExit = nerrorsOfAllPreviousCheckers; super.typeProcess(element, tree); if (getSubcheckers().size() > 0) { printCollectedMessages(tree.getCompilationUnit()); // Update errsOnLastExit to reflect the errors issued. this.errsOnLastExit = log.nerrors; } } /** * Stores all messages issued by this checker and its subcheckers for the current compilation * unit. The messages are printed after all checkers have processed the current compilation * unit. If this checker has no subcheckers and is not a subchecker for any other checker, then * messageStore is null and messages will be printed as they are issued by this checker. */ private TreeSet<CheckerMessage> messageStore = null; /** * If this is a compound checker or a subchecker of a compound checker, then the message is * stored until all messages from all checkers for the compilation unit are issued. * * <p>Otherwise, it prints the message. */ @Override protected void printMessage( Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { assert this.currentRoot == root; if (messageStore == null) { super.printMessage(kind, message, source, root); } else { CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this); messageStore.add(checkerMessage); } } /** * Prints error messages for this checker and all subcheckers such that the errors are ordered * by line and column number and then by checker. (See checkerMessageComparator for more precise * order.) * * @param unit Current compilation unit */ private void printCollectedMessages(CompilationUnitTree unit) { if (messageStore != null) { for (CheckerMessage msg : messageStore) { super.printMessage(msg.kind, msg.message, msg.source, unit); } } } /** Represents a message (e.g., an error message) issued by a checker. */ private static class CheckerMessage { final Diagnostic.Kind kind; final String message; final Tree source; /** * This checker that issued this message. The compound checker that depends on this checker * uses this to sort the messages. */ final BaseTypeChecker checker; private CheckerMessage( Diagnostic.Kind kind, String message, Tree source, BaseTypeChecker checker) { this.kind = kind; this.message = message; this.source = source; this.checker = checker; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CheckerMessage that = (CheckerMessage) o; if (kind != that.kind) { return false; } if (!message.equals(that.message)) { return false; } if (source == that.source) { return false; } return checker == that.checker; } @Override public int hashCode() { int result = kind.hashCode(); result = 31 * result + message.hashCode(); result = 31 * result + source.hashCode(); result = 31 * result + checker.hashCode(); return result; } @Override public String toString() { return "CheckerMessage{" + "kind=" + kind + ", checker=" + checker.getClass().getSimpleName() + ", message='" + message + '\'' + ", source=" + source + '}'; } } @Override public void typeProcessingOver() { for (BaseTypeChecker checker : getSubcheckers()) { checker.typeProcessingOver(); } super.typeProcessingOver(); } @Override public Set<String> getSupportedOptions() { Set<String> options = new HashSet<String>(); options.addAll(super.getSupportedOptions()); for (BaseTypeChecker checker : getSubcheckers()) { options.addAll(checker.getSupportedOptions()); } options.addAll( expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); return Collections.<String>unmodifiableSet(options); } @Override public Map<String, String> getOptions() { Map<String, String> options = new HashMap<String, String>(super.getOptions()); for (BaseTypeChecker checker : getSubcheckers()) { options.putAll(checker.getOptions()); } return options; } @Override protected Object processArg(Object arg) { if (arg instanceof Collection) { List<Object> newList = new LinkedList<>(); for (Object o : ((Collection<?>) arg)) { newList.add(processArg(o)); } return newList; } else if (arg instanceof AnnotationMirror) { return getTypeFactory() .getAnnotationFormatter() .formatAnnotationMirror((AnnotationMirror) arg); } else { return super.processArg(arg); } } @Override protected boolean shouldAddShutdownHook() { if (super.shouldAddShutdownHook() || getTypeFactory().getCFGVisualizer() != null) { return true; } for (BaseTypeChecker checker : getSubcheckers()) { if (checker.getTypeFactory().getCFGVisualizer() != null) { return true; } } return false; } @Override protected void shutdownHook() { super.shutdownHook(); CFGVisualizer<?, ?, ?> viz = getTypeFactory().getCFGVisualizer(); if (viz != null) { viz.shutdown(); } for (BaseTypeChecker checker : getSubcheckers()) { viz = checker.getTypeFactory().getCFGVisualizer(); if (viz != null) { viz.shutdown(); } } } }