blob: 475beeb8e8719329fe064d5a9061a01707989799 [file] [log] [blame]
package org.checkerframework.common.basetype;
import com.google.common.collect.ImmutableSet;
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.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.common.reflection.MethodValChecker;
import org.checkerframework.dataflow.cfg.visualize.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.framework.util.TreePathCacher;
import org.checkerframework.javacutil.AbstractTypeProcessor;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.UserError;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.StringsPlume;
/**
* 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 default 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.
*
* @checker_framework.manual #creating-compiler-interface The checker class
*/
public abstract class BaseTypeChecker extends SourceChecker {
@Override
public void initChecker() {
// initialize all checkers and share options as necessary
for (BaseTypeChecker checker : getSubcheckers()) {
// 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());
// initChecker validates the passed options, so call it after setting supported options and
// lints.
checker.initChecker();
}
if (!getSubcheckers().isEmpty()) {
messageStore = new TreeSet<>(this::compareCheckerMessages);
}
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 @MonotonicNonNull 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.
*
* <p>Does not need to be initialized to null or an empty list because it is always initialized
* via calls to instantiateSubcheckers.
*/
// Set to non-null when subcheckers is.
private @MonotonicNonNull List<BaseTypeChecker> immediateSubcheckers = null;
/** Supported options for this checker. */
private @MonotonicNonNull Set<String> supportedOptions = null;
/** Options passed to this checker. */
private @MonotonicNonNull Map<String, String> options = null;
/**
* TreePathCacher to share between instances. Initialized either in getTreePathCacher (which is
* also called from instantiateSubcheckers).
*/
private TreePathCacher treePathCacher = null;
/**
* The list of suppress warnings prefixes supported by this checker or any of its subcheckers
* (including indirect subcheckers). Do not access this field directly; instead, use {@link
* #getSuppressWarningsPrefixesOfSubcheckers}.
*/
private @MonotonicNonNull Collection<String> suppressWarningsPrefixesOfSubcheckers = null;
@Override
protected void setRoot(CompilationUnitTree newRoot) {
super.setRoot(newRoot);
if (parentChecker == null) {
// Only clear the path cache if this is the main checker.
treePathCacher.clear();
}
}
/**
* 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 #printOrStoreMessage(Diagnostic.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, but other clients do
* modify the list.
*
* @return the subchecker classes on which this checker depends
*/
protected LinkedHashSet<Class<? extends BaseTypeChecker>> getImmediateSubcheckerClasses() {
if (shouldResolveReflection()) {
return new LinkedHashSet<>(Collections.singleton(MethodValChecker.class));
}
// The returned set will be modified by callees.
return new LinkedHashSet<>();
}
/**
* Returns whether or not reflection should be resolved.
*
* @return true if reflection should be resolved
*/
public boolean shouldResolveReflection() {
return hasOptionNoSubcheckers("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) {
BaseTypeVisitor<?> result =
invokeConstructorFor(
BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"),
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);
}
/**
* A public variant of {@link #createSourceVisitor}. Only use this if you know what you are doing.
*
* @return the type-checking visitor
*/
public BaseTypeVisitor<?> createSourceVisitorPublic() {
return createSourceVisitor();
}
/**
* Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by
* {@code replacement}.
*
* @param checkerClass the checker class
* @param replacement the string to replace "Checker" or "Subchecker" by
* @return the name of the related class
*/
@SuppressWarnings("signature") // string manipulation of @ClassGetName string
public static @ClassGetName String getRelatedClassName(
Class<?> checkerClass, String replacement) {
return checkerClass
.getName()
.replace("Checker", replacement)
.replace("Subchecker", replacement);
}
// **********************************************************************
// Misc. methods
// **********************************************************************
/** Specify supported lint options for all type-checkers. */
@Override
public Set<String> getSupportedLintOptions() {
Set<String> lintSet = new HashSet<>(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. Otherwise,
* throws an exception if there is trouble with the constructor invocation.
*
* @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 class does not
* exist
*/
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // Intentional abuse
public static <T> T invokeConstructorFor(
@ClassGetName 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();
if (err instanceof UserError || err instanceof TypeSystemError) {
// Don't add more information about the constructor invocation.
throw (RuntimeException) err;
}
}
Throwable cause;
String causeMessage;
if (t instanceof InvocationTargetException) {
cause = t.getCause();
if (cause == null || cause.getMessage() == null) {
causeMessage = t.getMessage();
} else if (t.getMessage() == null) {
causeMessage = cause.getMessage();
} else {
causeMessage = t.getMessage() + ": " + cause.getMessage();
}
} else {
cause = t;
causeMessage = (cause == null) ? "null" : cause.getMessage();
}
throw new BugInCF(
cause,
"Error when invoking constructor %s(%s) on args %s; cause: %s",
name,
StringsPlume.join(", ", paramTypes),
Arrays.toString(args),
causeMessage);
}
}
@Override
public BaseTypeVisitor<?> getVisitor() {
return (BaseTypeVisitor<?>) super.getVisitor();
}
/**
* Return the type factory associated with this checker.
*
* @return the type factory associated with this checker
*/
public GenericAnnotatedTypeFactory<?, ?, ?, ?> getTypeFactory() {
BaseTypeVisitor<?> visitor = getVisitor();
// Avoid NPE if this method is called during initialization.
if (visitor == null) {
return null;
}
return visitor.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() == 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();
if (classesOfImmediateSubcheckers.isEmpty()) {
return Collections.emptyList();
}
ArrayList<BaseTypeChecker> immediateSubcheckers =
new ArrayList<>(classesOfImmediateSubcheckers.size());
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;
}
BaseTypeChecker instance;
try {
instance = subcheckerClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new BugInCF("Could not create an instance of " + subcheckerClass);
}
instance.setProcessingEnvironment(this.processingEnv);
instance.treePathCacher = this.getTreePathCacher();
// Prevent the new checker from storing non-immediate subcheckers
instance.subcheckers = Collections.emptyList();
immediateSubcheckers.add(instance);
instance.immediateSubcheckers =
instance.instantiateSubcheckers(alreadyInitializedSubcheckerMap);
instance.setParentChecker(this);
alreadyInitializedSubcheckerMap.put(subcheckerClass, instance);
}
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.
*
* @return the list of all subcheckers (if any)
*/
public List<BaseTypeChecker> getSubcheckers() {
if (subcheckers == null) {
// Instantiate the checkers this one depends on, if any.
LinkedHashMap<Class<? extends BaseTypeChecker>, BaseTypeChecker> checkerMap =
new LinkedHashMap<>(1);
immediateSubcheckers = instantiateSubcheckers(checkerMap);
subcheckers = Collections.unmodifiableList(new ArrayList<>(checkerMap.values()));
}
return subcheckers;
}
/** Get the shared TreePathCacher instance. */
public TreePathCacher getTreePathCacher() {
if (treePathCacher == null) {
// In case it wasn't already set in instantiateSubcheckers.
treePathCacher = new TreePathCacher();
}
return treePathCacher;
}
@Override
protected void reportJavacError(TreePath p) {
if (parentChecker == null) {
// Only the parent checker should report the "type.checking.not.run" error.
super.reportJavacError(p);
}
}
// AbstractTypeProcessor delegation
@Override
public void typeProcess(TypeElement element, TreePath tree) {
if (!getSubcheckers().isEmpty()) {
// TODO: I expected this to only be necessary if (parentChecker == null).
// However, the NestedAggregateChecker fails otherwise.
messageStore.clear();
}
// 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.
// 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().isEmpty()) {
printStoredMessages(tree.getCompilationUnit());
// Update errsOnLastExit to reflect the errors issued.
this.errsOnLastExit = log.nerrors;
}
}
/**
* Like {@link SourceChecker#getSuppressWarningsPrefixes()}, but includes all prefixes supported
* by this checker or any of its subcheckers. Does not guarantee that the result is in any
* particular order. The result is immutable.
*
* @return the suppress warnings prefixes supported by this checker or any of its subcheckers
*/
public Collection<String> getSuppressWarningsPrefixesOfSubcheckers() {
if (this.suppressWarningsPrefixesOfSubcheckers == null) {
Collection<String> prefixes = getSuppressWarningsPrefixes();
for (BaseTypeChecker subchecker : getSubcheckers()) {
prefixes.addAll(subchecker.getSuppressWarningsPrefixes());
}
this.suppressWarningsPrefixesOfSubcheckers = ImmutableSet.copyOf(prefixes);
}
return this.suppressWarningsPrefixesOfSubcheckers;
}
/** A cache for {@link #getUltimateParentChecker}. */
@MonotonicNonNull BaseTypeChecker ultimateParentChecker;
/**
* Finds the ultimate parent checker of this checker. The ultimate parent checker is the checker
* that the user actually requested, i.e. the one with no parent. The ultimate parent might be
* this checker itself.
*
* @return the first checker in the parent checker chain with no parent checker of its own, i.e.
* the ultimate parent checker
*/
public BaseTypeChecker getUltimateParentChecker() {
if (ultimateParentChecker == null) {
ultimateParentChecker = this;
while (ultimateParentChecker.getParentChecker() instanceof BaseTypeChecker) {
ultimateParentChecker = (BaseTypeChecker) ultimateParentChecker.getParentChecker();
}
}
return ultimateParentChecker;
}
/**
* {@inheritDoc}
*
* <p>This implementation collects needed warning suppressions for all subcheckers.
*/
@Override
protected void warnUnneededSuppressions() {
if (parentChecker != null) {
return;
}
if (!hasOption("warnUnneededSuppressions")) {
return;
}
Set<Element> elementsWithSuppressedWarnings =
new HashSet<>(this.elementsWithSuppressedWarnings);
this.elementsWithSuppressedWarnings.clear();
Set<String> prefixes = new HashSet<>(getSuppressWarningsPrefixes());
Set<String> errorKeys = new HashSet<>(messagesProperties.stringPropertyNames());
for (BaseTypeChecker subChecker : subcheckers) {
elementsWithSuppressedWarnings.addAll(subChecker.elementsWithSuppressedWarnings);
subChecker.elementsWithSuppressedWarnings.clear();
prefixes.addAll(subChecker.getSuppressWarningsPrefixes());
errorKeys.addAll(subChecker.messagesProperties.stringPropertyNames());
subChecker.getVisitor().treesWithSuppressWarnings.clear();
}
warnUnneededSuppressions(elementsWithSuppressedWarnings, prefixes, errorKeys);
getVisitor().treesWithSuppressWarnings.clear();
}
/**
* 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.
* The purpose is to sort messages, grouping together all messages about a particular line of
* code.
*
* <p>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 printOrStoreMessage(
Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) {
assert this.currentRoot == root;
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (messageStore == null) {
super.printOrStoreMessage(kind, message, source, root, trace);
} else {
CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace);
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 {@link #compareCheckerMessages} for more
* precise order.)
*
* @param unit current compilation unit
*/
private void printStoredMessages(CompilationUnitTree unit) {
for (CheckerMessage msg : messageStore) {
super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace);
}
}
/** Represents a message (e.g., an error message) issued by a checker. */
private static class CheckerMessage {
/** The severity of the message. */
final Diagnostic.Kind kind;
/** The message itself. */
final String message;
/** The source code that the message is about. */
final @InternedDistinct Tree source;
/** Stores the stack trace when the message is created. */
final StackTraceElement[] trace;
/**
* The checker that issued this message. The compound checker that depends on this checker uses
* this to sort the messages.
*/
final @InternedDistinct BaseTypeChecker checker;
/**
* Create a new CheckerMessage.
*
* @param kind kind of diagnostic, for example, error or warning
* @param message error message that needs to be printed
* @param source tree element causing the error
* @param checker the type-checker in use
* @param trace the stack trace when the message is created
*/
private CheckerMessage(
Diagnostic.Kind kind,
String message,
@FindDistinct Tree source,
@FindDistinct BaseTypeChecker checker,
StackTraceElement[] trace) {
this.kind = kind;
this.message = message;
this.source = source;
this.checker = checker;
this.trace = trace;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CheckerMessage that = (CheckerMessage) o;
return this.kind == that.kind
&& this.message.equals(that.message)
&& this.source == that.source
&& this.checker == that.checker;
}
@Override
public int hashCode() {
return Objects.hash(kind, message, source, checker);
}
@Override
public String toString() {
return "CheckerMessage{"
+ "kind="
+ kind
+ ", checker="
+ checker.getClass().getSimpleName()
+ ", message='"
+ message
+ '\''
+ ", source="
+ source
+ '}';
}
}
/**
* Compares two {@link CheckerMessage}s. Compares first by position at which the error will be
* printed, then by kind of message, then by the message string, and finally by the order in which
* the checkers run.
*
* @param o1 the first CheckerMessage
* @param o2 the second CheckerMessage
* @return a negative integer, zero, or a positive integer if the first CheckerMessage is less
* than, equal to, or greater than the second
*/
private int compareCheckerMessages(CheckerMessage o1, CheckerMessage o2) {
int byPos = InternalUtils.compareDiagnosticPosition(o1.source, o2.source);
if (byPos != 0) {
return byPos;
}
int kind = o1.kind.compareTo(o2.kind);
if (kind != 0) {
return kind;
}
int msgcmp = o1.message.compareTo(o2.message);
if (msgcmp == 0) {
// If the two messages are identical so far, it doesn't matter
// from which checker they came.
return 0;
}
// Sort by order in which the checkers are run. (All the subcheckers,
// followed by the checker.)
List<BaseTypeChecker> subcheckers = BaseTypeChecker.this.getSubcheckers();
int o1Index = subcheckers.indexOf(o1.checker);
int o2Index = subcheckers.indexOf(o2.checker);
if (o1Index == -1) {
o1Index = subcheckers.size();
}
if (o2Index == -1) {
o2Index = subcheckers.size();
}
int checkercmp = Integer.compare(o1Index, o2Index);
if (checkercmp == 0) {
// If the two messages are from the same checker, sort by message.
return msgcmp;
} else {
return checkercmp;
}
}
@Override
public void typeProcessingOver() {
for (BaseTypeChecker checker : getSubcheckers()) {
checker.typeProcessingOver();
}
super.typeProcessingOver();
}
@Override
public Set<String> getSupportedOptions() {
if (supportedOptions == null) {
Set<String> options = new HashSet<>();
options.addAll(super.getSupportedOptions());
for (BaseTypeChecker checker : getSubcheckers()) {
options.addAll(checker.getSupportedOptions());
}
options.addAll(
expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0])));
supportedOptions = Collections.unmodifiableSet(options);
}
return supportedOptions;
}
@Override
public Map<String, String> getOptions() {
if (this.options == null) {
Map<String, String> options = new HashMap<>(super.getOptions());
for (BaseTypeChecker checker : getSubcheckers()) {
options.putAll(checker.getOptions());
}
this.options = Collections.unmodifiableMap(options);
}
return this.options;
}
/**
* Like {@link #getOptions}, but only includes options provided to this checker. Does not include
* those passed to subcheckers.
*
* @return the the active options for this checker, not including those passed to subcheckers
*/
public Map<String, String> getOptionsNoSubcheckers() {
return super.getOptions();
}
/**
* Like {@link #hasOption}, but checks whether the given option is provided to this checker. Does
* not consider those passed to subcheckers.
*
* @param name the name of the option to check
* @return true if the option name was provided to this checker, false otherwise
*/
public final boolean hasOptionNoSubcheckers(String name) {
return getOptionsNoSubcheckers().containsKey(name);
}
/**
* Return a list of additional stub files to be treated as if they had been written in a
* {@code @StubFiles} annotation.
*
* @return stub files to be treated as if they had been written in a {@code @StubFiles} annotation
*/
public List<String> getExtraStubFiles() {
return new ArrayList<>();
}
@Override
protected Object processArg(Object arg) {
if (arg instanceof Collection) {
Collection<?> carg = (Collection<?>) arg;
return CollectionsPlume.mapList(this::processArg, carg);
} else if (arg instanceof AnnotationMirror && getTypeFactory() != null) {
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();
}
}
}
}