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

framework.src.org.checkerframework.framework.source.SourceChecker 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.42.0
Show newest version
package org.checkerframework.framework.source;

/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.*;
*/

import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.util.CFContext;
import org.checkerframework.framework.util.CheckerMain;
import org.checkerframework.framework.util.OptionConfiguration;
import org.checkerframework.javacutil.AbstractTypeProcessor;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorHandler;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Pattern;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;

/**
 * An abstract annotation processor designed for implementing a
 * source-file checker for a JSR-308 conforming compiler plug-in. It provides an
 * interface to {@code javac}'s annotation processing API, routines for error
 * reporting via the JSR 199 compiler API, and an implementation for using a
 * {@link SourceVisitor} to perform the type-checking.
 *
 * 

* * Subclasses must implement the following methods: (TODO: update the list) * *

    *
  • {@link SourceChecker#getMessages} (for type-qualifier specific error messages) *
  • {@link SourceChecker#createSourceVisitor} (for a custom {@link SourceVisitor}) *
  • {@link SourceChecker#getSuppressWarningsKeys} (for honoring * {@literal @}{link SuppressWarnings} annotations) *
* * Most type-checker plug-ins will want to extend {@link BaseTypeChecker}, * instead of this class. Only checkers that require annotated types but not * subtype checking (e.g. for testing purposes) should extend this. * Non-type checkers (e.g. for enforcing coding styles) should extend * {@link AbstractProcessor} (or even this class) as the Checker Framework is * not designed for such checkers. */ @SupportedOptions({ // When adding a new standard option: // 1. Add a brief blurb here about the use case // and a pointer to one prominent use of the option. // 2. Update the Checker Framework manual: // * introduction.tex contains an overview of all options, which // should be in the same order as this source code file. // * a specific section should contain a detailed discussion. /// /// Unsound checking: ignore some errors /// // Set inclusion/exclusion of type uses or definitions // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar "skipUses", "onlyUses", "skipDefs", "onlyDefs", // A comma-separated list of warnings to suppress // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings "suppressWarnings", // With each warning, in addition to the concrete error key, // output the suppress warning keys that can be used to // suppress that warning. "showSuppressWarningKeys", // Unsoundly ignore side effects "assumeSideEffectFree", // Whether to ignore all subtype tests for type arguments that // were inferred for a raw type // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments "ignoreRawTypeArguments", // The next ones *increase* rather than *decrease* soundness. // They will eventually be replaced by their complements. // TODO: Checking of bodies of @SideEffectFree, @Deterministic, and // @Pure methods is temporarily disabled unless -AcheckPurityAnnotations is // supplied on the command line. // Re-enable it after making the analysis more precise. // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) "checkPurityAnnotations", // TODO: Temporary option to make array subtyping invariant, // which will be the new default soon. "invariantArrays", // TODO: Temporary option to make casts stricter, in particular when // casting to an array or generic type. This will be the new default soon. "checkCastElementType", // Whether to output errors or warnings only // org.checkerframework.framework.source.SourceChecker.report "warns", /// /// Type-checking modes: enable/disable functionality /// // Lint options // org.checkerframework.framework.source.SourceChecker.getSupportedLintOptions() and similar "lint", // Whether to suggest methods that could be marked @SideEffectFree, // @Deterministic, or @Pure // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) "suggestPureMethods", // Whether to assume that assertions are enabled or disabled // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder "assumeAssertionsAreEnabled", "assumeAssertionsAreDisabled", // Whether to assume sound concurrent semantics or // simplified sequential semantics // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics "concurrentSemantics", // Whether to use unchecked code defaults for bytecode and/or source code; these are configured // by the specific type checker using @Default[QualifierInHierarchy]InUncheckedCode[For]. // This option takes arguments "source" and/or "bytecode". // The default is "-source,-bytecode" (eventually this will be changed to "-source,bytecode"). // Note, if unchecked code defaults are turned on for source code, the unchecked // defaults are not applied to code in scope of an @AnnotatedFor. // See the "Compiling partially-annotated libraries" and // "Default qualifiers for \<.class> files (conservative library defaults)" // sections in the manual for more details // org.checkerframework.framework.source.SourceChecker.useUncheckedCodeDefault "useDefaultsForUncheckedCode", // Whether to resolve reflective method invocations // resolveReflection=debug cause debugging information // to be output. "resolveReflection", // Whether to use .jaif files whole-program inference "infer", /// /// Stub libraries /// // Additional stub files to use // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() "stubs", // Whether to print warnings about types/members in a stub file // that were not found on the class path // org.checkerframework.framework.stub.StubParser.warnIfNotFound "stubWarnIfNotFound", // Whether to print warnings about stub files that overwrite annotations // from bytecode. "stubWarnIfOverwritesBytecode", /// /// Debugging /// /// Amount of detail in messages // Whether to print @InvisibleQualifier marked annotations // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() "printAllQualifiers", // Print qualifier parameters using annotations instead of the <> format. "printQualifierParametersAsAnnotations", // Whether to print [] around a set of type parameters in order to clearly see where they end // e.g. // without this option the E is printed as: E extends F extends Object // with this option: E [ extends F [ extends Object super Void ] super Void ] // when multiple type variables are used this becomes useful very quickly "printVerboseGenerics", // Output detailed message in simple-to-parse format, useful // for tools parsing Checker Framework output. // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) "detailedmsgtext", // Whether to output a stack trace for a framework error // org.checkerframework.framework.source.SourceChecker.logCheckerError "printErrorStack", // Only output error code, useful for testing framework // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) "nomsgtext", /// Stub and JDK libraries // Ignore the standard jdk.astub file; primarily for testing or debugging. // org.checkerframework.framework.type.AnnotatedTypeFactory.parseStubFiles() "ignorejdkastub", // Whether to check that the annotated JDK is correctly provided // org.checkerframework.common.basetype.BaseTypeVisitor.checkForAnnotatedJdk() "nocheckjdk", // Whether to print debugging messages while processing the stub files // org.checkerframework.framework.stub.StubParser.debugStubParser "stubDebug", /// Progress tracing // Output file names before checking // TODO: it looks like support for this was lost! "filenames", // Output all subtyping checks // org.checkerframework.common.basetype.BaseTypeVisitor "showchecks", /// Miscellaneous debugging options // Mechanism to visualize the control flow graph (CFG). // The argument is a sequence of values or key-value pairs. // The first argument has to be the fully-qualified name of the // org.checkerframework.dataflow.cfg.CFGVisualizer implementation // that should be used. The remaining values or key-value pairs are // passed to CFGVisualizer.init. // For example: // -Acfgviz=MyViz,a,b=c,d // instantiates class MyViz and calls CFGVisualizer.init // with {"a" -> true, "b" -> "c", "d" -> true}. "cfgviz", // Directory for .dot files generated from the CFG visualization in // org.checkerframework.dataflow.cfg.DOTCFGVisualizer // as initialized by // org.checkerframework.framework.type.GenericAnnotatedTypeFactory.createCFGVisualizer() // -Aflowdotdir=xyz // is short-hand for // -Acfgviz=org.checkerframework.dataflow.cfg.DOTCFGVisualizer,outdir=xyz "flowdotdir", // Enable additional output in the CFG visualization. // -Averbosecfg // is short-hand for // -Acfgviz=MyClass,verbose "verbosecfg", // Whether to output resource statistics at JVM shutdown // org.checkerframework.framework.source.SourceChecker.shutdownHook() "resourceStats", // Set the cache size for caches in AnnotatedTypeFactory "atfCacheSize", // Sets AnnotatedTypeFactory shouldCache to false "atfDoNotCache" }) public abstract class SourceChecker extends AbstractTypeProcessor implements ErrorHandler, CFContext, OptionConfiguration { // TODO A checker should export itself through a separate interface, // and maybe have an interface for all the methods for which it's safe // to override. /** The @SuppressWarnings key that will suppress warnings for all checkers. */ protected static final String SUPPRESS_ALL_KEY = "all"; /** File name of the localized messages. */ protected static final String MSGS_FILE = "messages.properties"; /** Maps error keys to localized/custom error messages. */ protected Properties messages; /** Used to report error messages and warnings via the compiler. */ protected Messager messager; /** Used as a helper for the {@link SourceVisitor}. */ protected Trees trees; /** The source tree that is being scanned. */ protected CompilationUnitTree currentRoot; /** If an error is detected in a CompilationUnitTree, skip * all future calls of typeProcess with that same CompilationUnitTree. */ private CompilationUnitTree previousErrorCompilationUnit; /** The visitor to use. */ protected SourceVisitor visitor; /** Keys for warning suppressions specified on the command line */ private String /*@Nullable*/ [] suppressWarnings; /** * Regular expression pattern to specify Java classes that are not * annotated, so warnings about uses of them should be suppressed. * * It contains the pattern specified by the user, through the option * {@code checkers.skipUses}; otherwise it contains a pattern that can * match no class. */ private Pattern skipUsesPattern; /** * Regular expression pattern to specify Java classes that are * annotated, so warnings about them should be issued but warnings * about all other classes should be suppressed. * * It contains the pattern specified by the user, through the option * {@code checkers.onlyUses}; otherwise it contains a pattern * matches every class. */ private Pattern onlyUsesPattern; /** * Regular expression pattern to specify Java classes whose * definition should not be checked. * * It contains the pattern specified by the user, through the option * {@code checkers.skipDefs}; otherwise it contains a pattern that can * match no class. */ private Pattern skipDefsPattern; /** * Regular expression pattern to specify Java classes whose * definition should be checked. * * It contains the pattern specified by the user, through the option * {@code checkers.onlyDefs}; otherwise it contains a pattern that * matches every class. */ private Pattern onlyDefsPattern; /** The supported lint options */ private Set supportedLints; /** The enabled lint options */ private Set activeLints; /** The active options for this checker. * This is a processed version of {@link ProcessingEnvironment#getOptions()}: * If the option is of the form "-ACheckerName@key=value" and the current checker class, * or one of its superclasses is named "CheckerName", then add key → value. * If the option is of the form "-ACheckerName@key=value" and the current checker class, * and none of its superclasses is named "CheckerName", then do not add key → value. * If the option is of the form "-Akey=value", then add key → value. * * Both the simple and the canonical name of the checker can be used. * Superclasses of the current checker are also considered. */ private Map activeOptions; /** The string that separates the checker name from the option name. * This string may only consist of valid Java identifier part characters, * because it will be used within the key of an option. */ private final static String OPTION_SEPARATOR = "_"; /** The line separator */ private final static String LINE_SEPARATOR = System.getProperty("line.separator").intern(); /** The checker that called this one, whether that be a BaseTypeChecker (used * as a compound checker) or an AggregateChecker. * Null if this is the checker that calls all others. * Note that in the case of a compound checker, the compound checker is the * parent, not the checker that was run prior to this one by the compound * checker. */ protected SourceChecker parentChecker = null; /** List of upstream checker names. * Includes the current checker. */ protected List upstreamCheckerNames = null; @Override public final void init(ProcessingEnvironment env) { super.init(env); // The processingEnvironment field will also be set by the superclass' init method. // This is used to trigger AggregateChecker's setProcessingEnvironment. setProcessingEnvironment(env); } /** * @return the {@link ProcessingEnvironment} that was supplied to this * checker */ @Override // from CFChecker public ProcessingEnvironment getProcessingEnvironment() { return this.processingEnv; } /* This method is protected only to allow the AggregateChecker and BaseTypeChecker to call it. */ protected void setProcessingEnvironment(ProcessingEnvironment env) { this.processingEnv = env; } protected void setParentChecker(SourceChecker parentChecker) { this.parentChecker = parentChecker; } /** * Return a list containing this checker name and all checkers it is a * part of (that is, checkers that called it). */ public List getUpstreamCheckerNames() { if (upstreamCheckerNames == null) { upstreamCheckerNames = new ArrayList(); SourceChecker checker = this; while (checker != null) { upstreamCheckerNames.add(checker.getClass().getName()); checker = checker.parentChecker; } } return upstreamCheckerNames; } /** @return the {@link CFContext} used by this checker */ public CFContext getContext() { return this; } @Override public SourceChecker getChecker() { return this; } @Override public OptionConfiguration getOptionConfiguration() { return this; } @Override public Elements getElementUtils() { return getProcessingEnvironment().getElementUtils(); } @Override public Types getTypeUtils() { return getProcessingEnvironment().getTypeUtils(); } @Override public Trees getTreeUtils() { return Trees.instance(getProcessingEnvironment()); } @Override public SourceVisitor getVisitor() { return this.visitor; } /** * Provides the {@link SourceVisitor} that the checker should use to scan * input source trees. * * @return a {@link SourceVisitor} to use to scan source trees */ protected abstract SourceVisitor createSourceVisitor(); @Override public AnnotationProvider getAnnotationProvider() { throw new UnsupportedOperationException("getAnnotationProvider is not implemented for this class."); } /** * Provides a mapping of error keys to custom error messages. *

* As a default, this implementation builds a {@link Properties} out of * file {@code messages.properties}. It accumulates all the properties files * in the Java class hierarchy from the checker up to {@code SourceChecker}. * This permits subclasses to inherit default messages while being able to * override them. * * @return a {@link Properties} that maps error keys to error message text */ public Properties getMessages() { if (this.messages != null) { return this.messages; } this.messages = new Properties(); Stack> checkers = new Stack>(); Class currClass = this.getClass(); while (currClass != SourceChecker.class) { checkers.push(currClass); currClass = currClass.getSuperclass(); } checkers.push(SourceChecker.class); while (!checkers.empty()) { messages.putAll(getProperties(checkers.pop(), MSGS_FILE)); } return this.messages; } private Pattern getSkipPattern(String patternName, Map options) { // Default is an illegal Java identifier substring // so that it won't match anything. // Note that AnnotatedType's toString output format contains characters such as "():{}". return getPattern(patternName, options, "\\]'\"\\]"); } private Pattern getOnlyPattern(String patternName, Map options) { // default matches everything return getPattern(patternName, options, "."); } private Pattern getPattern(String patternName, Map options, String defaultPattern) { String pattern = ""; if (options.containsKey(patternName)) { pattern = options.get(patternName); } else if (System.getProperty("checkers." + patternName) != null) { pattern = System.getProperty("checkers." + patternName); } else if (System.getenv(patternName) != null) { pattern = System.getenv(patternName); } if (pattern.indexOf("/") != -1) { message(Kind.WARNING, "The " + patternName + " property contains \"/\", which will never match a class name: " + pattern); } if (pattern.equals("")) { pattern = defaultPattern; } return Pattern.compile(pattern); } private Pattern getSkipUsesPattern(Map options) { return getSkipPattern("skipUses", options); } private Pattern getOnlyUsesPattern(Map options) { return getOnlyPattern("onlyUses", options); } private Pattern getSkipDefsPattern(Map options) { return getSkipPattern("skipDefs", options); } private Pattern getOnlyDefsPattern(Map options) { return getOnlyPattern("onlyDefs", options); } // TODO: do we want this? // Cache the keys that we already warned about to prevent repetitions. // private Set warnedOnLint = new HashSet(); private Set createActiveLints(Map options) { if (!options.containsKey("lint")) { return Collections.emptySet(); } String lintString = options.get("lint"); if (lintString == null) { return Collections.singleton("all"); } Set activeLint = new HashSet(); for (String s : lintString.split(",")) { if (!this.getSupportedLintOptions().contains(s) && !(s.charAt(0) == '-' && this.getSupportedLintOptions().contains(s.substring(1))) && !s.equals("all") && !s.equals("none") /*&& !warnedOnLint.contains(s)*/) { this.messager.printMessage(javax.tools.Diagnostic.Kind.WARNING, "Unsupported lint option: " + s + "; All options: " + this.getSupportedLintOptions()); // warnedOnLint.add(s); } activeLint.add(s); if (s.equals("none")) { activeLint.add("-all"); } } return Collections.unmodifiableSet(activeLint); } private Map createActiveOptions(Map options) { if (options.isEmpty()) { return Collections.emptyMap(); } Map activeOpts = new HashMap(); for (Map.Entry opt : options.entrySet()) { String key = opt.getKey(); String value = opt.getValue(); String[] split = key.split(OPTION_SEPARATOR); switch (split.length) { case 1: // No separator, option always active activeOpts.put(key, value); break; case 2: // Valid class-option pair Class clazz = this.getClass(); do { if (clazz.getCanonicalName().equals(split[0]) || clazz.getSimpleName().equals(split[0])) { activeOpts.put(split[1], value); } clazz = clazz.getSuperclass(); } while (clazz != null && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); break; default: ErrorReporter.errorAbort("Invalid option name: " + key + " At most one separator " + OPTION_SEPARATOR + " expected, but found " + split.length); } } return Collections.unmodifiableMap(activeOpts); } private String /*@Nullable*/ [] createSuppressWarnings(Map options) { if (!options.containsKey("suppressWarnings")) { return null; } String swString = options.get("suppressWarnings"); if (swString == null) { return null; } return swString.split(","); } /** * Exception type used only internally to abort * processing. * Only public to allow tests.AnnotationBuilderTest; * this class should be private. TODO: nicer way? */ @SuppressWarnings("serial") public static class CheckerError extends RuntimeException { /** Whether this error is caused by a user error, e.g. incorrect command-line arguments. */ public final boolean userError; public CheckerError(String msg, Throwable cause, boolean userError) { super(msg, cause); this.userError = userError; } } /** * Log an error message and abort processing. * Call this method instead of raising an exception. * * @param msg the error message to log */ @Override public void errorAbort(String msg) { throw new CheckerError(msg, new Throwable(), false); } /** * Log an error message and abort processing. * Call this method instead of raising an exception. * * @param msg the error message to log * @param cause the original error cause */ @Override public void errorAbort(String msg, Throwable cause) { throw new CheckerError(msg, cause, false); } /** * Log a user error message and abort processing. * Call this method instead of raising an exception or * using System.out. * In contrast to {@link SourceChecker#errorAbort(String)} this method * presents a more user-friendly output. * * @param msg the error message to log */ public void userErrorAbort(String msg) { throw new CheckerError(msg, new Throwable(), true); } private void logCheckerError(CheckerError ce) { if (ce.getMessage() == null) { final String stackTrace = formatStackTrace(ce.getStackTrace()); ErrorReporter.errorAbort("Null error message while logging Checker error.\nStack Trace:\n" + stackTrace); } StringBuilder msg = new StringBuilder(ce.getMessage()); if ((processingEnv == null || processingEnv.getOptions() == null || processingEnv.getOptions().containsKey("printErrorStack")) && ce.getCause() != null) { if (this.currentRoot != null && this.currentRoot.getSourceFile() != null) { msg.append("\nCompilation unit: " + this.currentRoot.getSourceFile().getName()); } msg.append("\nException: " + ce.getCause().toString() + "; " + formatStackTrace(ce.getCause().getStackTrace())); Throwable cause = ce.getCause().getCause(); while (cause != null) { msg.append("\nUnderlying Exception: " + (cause.toString() + "; " + formatStackTrace(cause.getStackTrace()))); cause = cause.getCause(); } } else { if (ce.userError) { msg.append('.'); } else { msg.append("; invoke the compiler with -AprintErrorStack to see the stack trace."); } } if (this.messager == null) { messager = processingEnv.getMessager(); } this.messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, msg); } /** * {@inheritDoc} * * Type-checkers are not supposed to override this. * Instead use initChecker. * This allows us to handle CheckerError only here and doesn't * require all overriding implementations to be aware of CheckerError. * * @see AbstractProcessor#init(ProcessingEnvironment) * @see SourceChecker#initChecker() */ @Override public void typeProcessingStart() { try { super.typeProcessingStart(); initChecker(); if (this.messager == null) { messager = processingEnv.getMessager(); messager.printMessage( javax.tools.Diagnostic.Kind.WARNING, "You have forgotten to call super.initChecker in your " + "subclass of SourceChecker, " + this.getClass() + "! Please ensure your checker is properly initialized."); } if (shouldAddShutdownHook()) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { shutdownHook(); } }); } } catch (CheckerError ce) { logCheckerError(ce); } catch (Throwable t) { logCheckerError(wrapThrowableAsCheckerError("SourceChecker.typeProcessingStart", t, null)); } } /** * Initialize the checker. * * @see AbstractProcessor#init(ProcessingEnvironment) */ public void initChecker() { // Grab the Trees and Messager instances now; other utilities // (like Types and Elements) can be retrieved by subclasses. /*@Nullable*/ Trees trees = Trees.instance(processingEnv); assert trees != null; /*nninvariant*/ this.trees = trees; this.messager = processingEnv.getMessager(); this.messages = getMessages(); this.visitor = createSourceVisitor(); // TODO: hack to clear out static caches. // When the {@link org.checkerframework.qualframework.util.QualifierContext} // gets used by all utilities, this shouldn't be an issue anymore. AnnotationUtils.clear(); } /** * Return true to indicate that method {@link #shutdownHook} should be * added as a shutdownHook of the JVM. */ protected boolean shouldAddShutdownHook() { return hasOption("resourceStats"); } /** * Method that gets called exactly once at shutdown time of the JVM. * Checkers can override this method to customize the behavior. */ protected void shutdownHook() { if (hasOption("resourceStats")) { // Check for the "resourceStats" option and don't call shouldAddShutdownHook // to allow subclasses to override shouldXXX and shutdownHook and simply // call the super implementations. printStats(); } } /** Print resource usage statistics */ protected void printStats() { List memoryPools = ManagementFactory.getMemoryPoolMXBeans(); for (MemoryPoolMXBean memoryPool : memoryPools) { System.out.println("Memory pool " + memoryPool.getName() + " statistics"); System.out.println(" Pool type: " + memoryPool.getType()); System.out.println(" Peak usage: " + memoryPool.getPeakUsage()); } } /** * Output the warning about source level at most once. */ private boolean warnedAboutSourceLevel = false; /** * The number of errors at the last exit of the type processor. * At entry to the type processor we check whether the current error count is * higher and then don't process the file, as it contains some Java errors. * Needs to be protected to allow access from AggregateChecker and BaseTypeChecker. */ protected int errsOnLastExit = 0; /** * Type-check the code with Java specifications and then runs the Checker * Rule Checking visitor on the processed source. * * @see Processor#process(Set, RoundEnvironment) */ @Override public void typeProcess(TypeElement e, TreePath p) { if (e == null) { messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "Refusing to process empty TypeElement"); return; } if (p == null) { messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "Refusing to process empty TreePath in TypeElement: " + e); return; } Context context = ((JavacProcessingEnvironment)processingEnv).getContext(); com.sun.tools.javac.code.Source source = com.sun.tools.javac.code.Source.instance(context); if ((! warnedAboutSourceLevel) && (! source.allowTypeAnnotations())) { messager.printMessage(javax.tools.Diagnostic.Kind.WARNING, "-source " + source.name + " does not support type annotations"); warnedAboutSourceLevel = true; } Log log = Log.instance(context); if (log.nerrors > this.errsOnLastExit) { this.errsOnLastExit = log.nerrors; previousErrorCompilationUnit = p.getCompilationUnit(); return; } if (p.getCompilationUnit() == previousErrorCompilationUnit) { // If the same compilation unit was seen with an error before, // skip it. This is in particular necessary for Java errors, which // show up once, but further calls to typeProcess will happen. // See Issue 346. return; } else { previousErrorCompilationUnit = null; } if (p.getCompilationUnit() != currentRoot) { currentRoot = p.getCompilationUnit(); visitor.setRoot(currentRoot); } // Visit the attributed tree. try { visitor.visit(p); } catch (CheckerError ce) { logCheckerError(ce); } catch (Throwable t) { logCheckerError(wrapThrowableAsCheckerError("SourceChecker.typeProcess", t, p)); } finally { // Also add possibly deferred diagnostics, which will get published back in // AbstractTypeProcessor. this.errsOnLastExit = log.nerrors; } } private CheckerError wrapThrowableAsCheckerError(String where, Throwable t, /*@Nullable*/ TreePath p) { return new CheckerError( where + ": unexpected Throwable (" + t.getClass().getSimpleName() + ")" + ((p == null) ? "" : " while processing " + p.getCompilationUnit().getSourceFile().getName()) + (t.getMessage() == null ? "" : "; message: " + t.getMessage()), t, false); } /** * Format a list of {@link StackTraceElement}s to be printed out as an error * message. */ protected String formatStackTrace(StackTraceElement[] stackTrace) { boolean first = true; StringBuilder sb = new StringBuilder(); if (stackTrace.length == 0) { sb.append("no stack trace available."); } else { sb.append("Stack trace: "); } for (StackTraceElement ste : stackTrace) { if (!first) { sb.append("\n"); } first = false; sb.append(ste.toString()); } return sb.toString(); } // Uses private fields, need to rewrite. // public void dumpState() { // System.out.printf("SourceChecker = %s%n", this); // System.out.printf(" env = %s%n", env); // System.out.printf(" env.elementUtils = %s%n", ((JavacProcessingEnvironment) env).elementUtils); // System.out.printf(" env.elementUtils.types = %s%n", ((JavacProcessingEnvironment) env).elementUtils.types); // System.out.printf(" env.elementUtils.enter = %s%n", ((JavacProcessingEnvironment) env).elementUtils.enter); // System.out.printf(" env.typeUtils = %s%n", ((JavacProcessingEnvironment) env).typeUtils); // System.out.printf(" trees = %s%n", trees); // System.out.printf(" trees.enter = %s%n", ((com.sun.tools.javac.api.JavacTrees) trees).enter); // System.out.printf(" trees.elements = %s%n", ((com.sun.tools.javac.api.JavacTrees) trees).elements); // System.out.printf(" trees.elements.types = %s%n", ((com.sun.tools.javac.api.JavacTrees) trees).elements.types); // System.out.printf(" trees.elements.enter = %s%n", ((com.sun.tools.javac.api.JavacTrees) trees).elements.enter); // } /** * Returns the localized long message corresponding for this key, and * returns the defValue if no localized message is found. * */ protected String fullMessageOf(String messageKey, String defValue) { String key = messageKey; do { if (messages.containsKey(key)) { return messages.getProperty(key); } int dot = key.indexOf('.'); if (dot < 0) { return defValue; } key = key.substring(dot + 1); } while (true); } /** * Prints a message (error, warning, note, etc.) via JSR-269. * * @param kind * the type of message to print * @param source * the object from which to obtain source position information * @param msgKey * the message key to print * @param args * arguments for interpolation in the string corresponding to the * given message key * @see Diagnostic * @throws IllegalArgumentException * if {@code source} is neither a {@link Tree} nor an * {@link Element} */ public void message(Diagnostic.Kind kind, Object source, /*@CompilerMessageKey*/ String msgKey, Object... args) { assert messages != null : "null messages"; if (args != null) { for (int i = 0; i < args.length; ++i) { if (args[i] == null) { continue; } // Try to process the arguments args[i] = processArg(args[i]); } } if (kind == Diagnostic.Kind.NOTE) { System.err.println("(NOTE) " + String.format(msgKey, args)); return; } final String defaultFormat = String.format("(%s)", msgKey); String fmtString; if (this.processingEnv.getOptions() != null /*nnbug*/ && this.processingEnv.getOptions().containsKey("nomsgtext")) { fmtString = defaultFormat; } else if (this.processingEnv.getOptions() != null /*nnbug*/ && this.processingEnv.getOptions().containsKey("detailedmsgtext")) { // The -Adetailedmsgtext command-line option was given, so output // a stylized error message for easy parsing by a tool. StringBuilder sb = new StringBuilder(); // The parts, separated by " $$ " (DETAILS_SEPARATOR), are: // (1) error key // TODO: should we also have some type system identifier here? // E.g. Which subclass of SourceChecker we are? Or also the SuppressWarnings keys? sb.append(defaultFormat); sb.append(DETAILS_SEPARATOR); // (2) number of additional tokens, and those tokens; this // depends on the error message, and an example is the found // and expected types if (args != null) { sb.append(args.length); sb.append(DETAILS_SEPARATOR); for (Object arg : args) { sb.append(arg); sb.append(DETAILS_SEPARATOR); } } else { // Output 0 for null arguments. sb.append(0); sb.append(DETAILS_SEPARATOR); } // (3) The error position, as starting and ending characters in // the source file. final Tree tree; if (source instanceof Element) { tree = trees.getTree( (Element) source ); } else if (source instanceof Tree) { tree = (Tree) source; } else { tree = null; } sb.append( treeToFilePositionString( tree, currentRoot, processingEnv ) ); sb.append(DETAILS_SEPARATOR); // (4) The human-readable error message. sb.append(fullMessageOf(msgKey, defaultFormat)); fmtString = sb.toString(); } else { final String suppressing; if (this.processingEnv.getOptions().containsKey("showSuppressWarningKeys")) { suppressing = String.format("[%s:%s] ", this.getSuppressWarningsKeys(), msgKey); } else { suppressing = String.format("[%s] ", msgKey); } fmtString = suppressing + fullMessageOf(msgKey, defaultFormat); } String messageText; try { messageText = String.format(fmtString, args); } catch (Exception e) { messageText = "Invalid format string: \"" + fmtString + "\" args: " + Arrays.toString(args); } if (LINE_SEPARATOR != "\n") { // interned // Replace '\n' with the proper line separator messageText = messageText.replaceAll("\n", LINE_SEPARATOR); } if (source instanceof Element) { messager.printMessage(kind, messageText, (Element) source); } else if (source instanceof Tree) { Trees.instance(processingEnv).printMessage(kind, messageText, (Tree) source, currentRoot); } else { ErrorReporter.errorAbort("invalid position source: " + source.getClass().getName()); } } /** * Process an argument to an error message before it is passed to String.format. * @param arg the argument * @return the result after processing */ protected Object processArg(Object arg) { // Check to see if the argument itself is a property to be expanded return messages.getProperty(arg.toString(), arg.toString()); } /** * Print a non-localized message using the javac messager. * This is preferable to using System.out or System.err, but should * only be used for exceptional cases that don't happen in correct usage. * Localized messages should be raised using * {@link SourceChecker#message(Diagnostic.Kind, Object, String, Object...)}. * * @param kind the kind of message to print * @param msg the message text * @param args optional arguments to substitute in the message. * * @see SourceChecker#message(Diagnostic.Kind, Object, String, Object...) */ public void message(Diagnostic.Kind kind, String msg, Object... args) { if (messager != null) { messager.printMessage(kind, String.format(msg, args)); } else { System.err.println(kind + ": " + String.format(msg, args)); } } /** * For the given tree, compute the source positions for that tree. Return a "tuple" like string * (e.g. "( 1, 200 )" ) that contains the start and end position of the tree in the current compilation unit. * * @param tree tree to locate within the current compilation unit * @param currentRoot the current compilation unit * @param processingEnv the current processing environment * @return a tuple string representing the range of characters that tree occupies in the source file */ public String treeToFilePositionString(Tree tree, CompilationUnitTree currentRoot, ProcessingEnvironment processingEnv) { if (tree == null) { return null; } SourcePositions sourcePositions = trees.getSourcePositions(); long start = sourcePositions.getStartPosition( currentRoot, tree); long end = sourcePositions.getEndPosition( currentRoot, tree ); return "( " + start + ", " + end + " )"; } public static final String DETAILS_SEPARATOR = " $$ "; /** * Determines whether an error (whose error key is {@code errKey}) should * be suppressed, according to the user's explicitly-written * SuppressWarnings annotation {@code anno} or the -AsuppressWarnings * command-line argument. *

* * A @SuppressWarnings value may be of the following pattern: * *

    *
  1. {@code "suppress-key"}, where suppress-key is a supported warnings * key, as specified by {@link #getSuppressWarningsKeys()} * (e.g., {@code "nullness"} for Nullness, {@code "regex"} for Regex)
  2. * *
  3. {@code "suppress-key:error-key}, where the suppress-key * is as above, and error-key is a prefix of the errors * that it may suppress. So "nullness:generic.argument", would * suppress any errors in the Nullness Checker related to * generic.argument.
  4. *
* * @param anno the @SuppressWarnings annotation written by the user * @param errKey the error key the checker is emitting * @return true if one of {@code anno}'s keys is * returned by {@link SourceChecker#getSuppressWarningsKeys}; * also accounts for errKey */ private boolean checkSuppressWarnings(/*@Nullable*/ SuppressWarnings anno, String errKey) { // Don't suppress warnings if this checker provides no key to do so. Collection checkerSwKeys = this.getSuppressWarningsKeys(); if (checkerSwKeys.isEmpty()) { return false; } String[] userSwKeys = (anno == null ? null : anno.value()); if (this.suppressWarnings == null) { this.suppressWarnings = createSuppressWarnings(getOptions()); } String[] cmdLineSwKeys = this.suppressWarnings; return checkSuppressWarnings(userSwKeys, errKey) || checkSuppressWarnings(cmdLineSwKeys, errKey); } /** * Return true if the given error should be suppressed, based on the * given @SuppressWarnings keys. * @param userSwKeys the @SuppressWarnings keys supplied by the user * @param errKey the error key the checker is emitting * @return true if one of the {@code userSwKeys} is * returned by {@link SourceChecker#getSuppressWarningsKeys}; * also accounts for errKey */ private boolean checkSuppressWarnings(String /*@Nullable*/ [] userSwKeys, String errKey) { if (userSwKeys == null) { return false; } Collection checkerSwKeys = this.getSuppressWarningsKeys(); // Check each value of the user-written @SuppressWarnings annotation. for (String suppressWarningValue : userSwKeys) { for (String checkerKey : checkerSwKeys) { if (suppressWarningValue.equalsIgnoreCase(checkerKey)) { return true; } String expected = checkerKey + ":" + errKey; if (expected.toLowerCase().contains(suppressWarningValue.toLowerCase())) { return true; } } } return false; } /** * Determines whether all the warnings pertaining to a given tree * should be suppressed. Returns true if the tree is within the scope * of a @SuppressWarnings annotation, one of whose values suppresses * the checker's warnings. The list of keys that suppress a checker's * warnings is provided by the {@link * SourceChecker#getSuppressWarningsKeys} method. * * @param tree the tree that might be a source of a warning * @param errKey the error key the checker is emitting * @return true if no warning should be emitted for the given tree because * it is contained by a declaration with an appropriately-valued * {@literal @}SuppressWarnings annotation; false otherwise */ // Public so it can be called from a few places in // org.checkerframework.framework.flow.CFAbstractTransfer public boolean shouldSuppressWarnings(Tree tree, String errKey) { // Don't suppress warnings if this checker provides no key to do so. Collection checkerKeys = this.getSuppressWarningsKeys(); if (checkerKeys.isEmpty()) { return false; } /*@Nullable*/ TreePath path = trees.getPath(this.currentRoot, tree); if (path == null) { return false; } /*@Nullable*/ VariableTree var = TreeUtils.enclosingVariable(path); if (var != null && shouldSuppressWarnings(InternalUtils.symbol(var), errKey)) { return true; } /*@Nullable*/ MethodTree method = TreeUtils.enclosingMethod(path); if (method != null) { /*@Nullable*/ Element elt = InternalUtils.symbol(method); if (shouldSuppressWarnings(elt, errKey)) { return true; } if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { // Return false immediately. Do NOT check for AnnotatedFor in // the enclosing elements, because they may not have an // @AnnotatedFor. return false; } } /*@Nullable*/ ClassTree cls = TreeUtils.enclosingClass(path); if (cls != null) { /*@Nullable*/ Element elt = InternalUtils.symbol(cls); if (shouldSuppressWarnings(elt, errKey)) { return true; } if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { // Return false immediately. Do NOT check for AnnotatedFor in // the enclosing elements, because they may not have an // @AnnotatedFor. return false; } } if (useUncheckedCodeDefault("source")) { // If we got this far without hitting an @AnnotatedFor and returning // false, we DO suppress the warning. return true; } return false; } /** * Should unchecked code defaults be used for the kind of code indicated by the parameter * @param kindOfCode source or bytecode * @return whether unchecked code defaults should be used */ public boolean useUncheckedCodeDefault(String kindOfCode) { final boolean useUncheckedDefaultsForSource = false; final boolean useUncheckedDefaultsForByteCode = false; String option = this.getOption("useDefaultsForUncheckedCode"); String[] args = option != null ? option.split(",") : new String[0]; for (String arg : args) { boolean value = arg.indexOf("-") != 0; arg = value ? arg : arg.substring(1); if (arg.equals(kindOfCode)) { return value; } } if (kindOfCode.equals("source")) { return useUncheckedDefaultsForSource; } else if (kindOfCode.equals("bytecode")) { return useUncheckedDefaultsForByteCode; } else { ErrorReporter.errorAbort("SourceChecker: unexpected argument to useUncheckedCodeDefault: " + kindOfCode); } return false; } /** * Determines whether all the warnings pertaining to a given tree * should be suppressed. Returns true if the element is within the scope * of a @SuppressWarnings annotation, one of whose values suppresses * the checker's warnings. The list of keys that suppress a checker's * warnings is provided by the {@link * SourceChecker#getSuppressWarningsKeys} method. * * @param elt the Element that might be a source of, or related to, a warning * @param errKey the error key the checker is emitting * @return true if no warning should be emitted for the given Element because * it is contained by a declaration with an appropriately-valued * {@code @SuppressWarnings} annotation; false otherwise */ // Public so it can be called from InitializationVisitor.checkerFieldsInitialized public boolean shouldSuppressWarnings(/*@Nullable*/ Element elt, String errKey) { if (elt == null) { return false; } if (checkSuppressWarnings(elt.getAnnotation(SuppressWarnings.class), errKey)) { return true; } if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { // Return false immediately. Do NOT check for AnnotatedFor in the // enclosing elements, because they may not have an @AnnotatedFor. return false; } return shouldSuppressWarnings(elt.getEnclosingElement(), errKey); } private boolean isAnnotatedForThisCheckerOrUpstreamChecker(/*@Nullable*/ Element elt) { if (elt == null || !useUncheckedCodeDefault("source")) { return false; } /*@Nullable*/ AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); String[] userAnnotatedFors = (anno == null ? null : anno.value()); if (userAnnotatedFors != null) { List upstreamCheckerNames = getUpstreamCheckerNames(); for (String userAnnotatedFor : userAnnotatedFors) { if (CheckerMain.matchesCheckerOrSubcheckerFromList(userAnnotatedFor, upstreamCheckerNames)) { return true; } } } return false; } /** * Reports a result. By default, it prints it to the screen via the * compiler's internal messenger if the result is non-success; otherwise, * the method returns with no side-effects. * * @param r * the result to report * @param src * the position object associated with the result */ public void report(final Result r, final Object src) { String errKey = r.getMessageKeys().iterator().next(); if (src instanceof Tree && shouldSuppressWarnings((Tree)src, errKey)) { return; } if (src instanceof Element && shouldSuppressWarnings((Element)src, errKey)) { return; } if (r.isSuccess()) { return; } for (Result.DiagMessage msg : r.getDiagMessages()) { if (r.isFailure()) { this.message(hasOption("warns") ? Diagnostic.Kind.MANDATORY_WARNING : Diagnostic.Kind.ERROR, src, msg.getMessageKey(), msg.getArgs()); } else if (r.isWarning()) { this.message(Diagnostic.Kind.MANDATORY_WARNING, src, msg.getMessageKey(), msg.getArgs()); } else { this.message(Diagnostic.Kind.NOTE, src, msg.getMessageKey(), msg.getArgs()); } } } /** * Determines the value of the lint option with the given name. Just * as javac * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, * annotation-related lint options are enabled with "-Alint:xxx" and * disabled with "-Alint:-xxx". * * @throws IllegalArgumentException if the option name is not recognized * via the {@link SupportedLintOptions} annotation or the {@link * SourceChecker#getSupportedLintOptions} method * @param name the name of the lint option to check for * @return true if the lint option was given, false if it was not given or * was given prepended with a "-" * * @see SourceChecker#getLintOption(String, boolean) */ public final boolean getLintOption(String name) { return getLintOption(name, false); } /** * Determines the value of the lint option with the given name. Just * as javac * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, * annotation-related lint options are enabled with "-Alint=xxx" and * disabled with "-Alint=-xxx". * * @throws IllegalArgumentException if the option name is not recognized * via the {@link SupportedLintOptions} annotation or the {@link * SourceChecker#getSupportedLintOptions} method * @param name the name of the lint option to check for * @param def the default option value, returned if the option was not given * @return true if the lint option was given, false if it was given * prepended with a "-", or {@code def} if it was not given at all * * @see SourceChecker#getLintOption(String) * @see SourceChecker#getOption(String) */ public final boolean getLintOption(String name, boolean def) { if (!this.getSupportedLintOptions().contains(name)) { ErrorReporter.errorAbort("Illegal lint option: " + name); } if (activeLints == null) { activeLints = createActiveLints(processingEnv.getOptions()); } if (activeLints.isEmpty()) { return def; } String tofind = name; while (tofind != null) { if (activeLints.contains(tofind)) { return true; } else if (activeLints.contains(String.format("-%s", tofind))) { return false; } tofind = parentOfOption(tofind); } return def; } /** * Set the value of the lint option with the given name. Just * as javac * uses "-Xlint:xxx" to enable and "-Xlint:-xxx" to disable option xxx, * annotation-related lint options are enabled with "-Alint=xxx" and * disabled with "-Alint=-xxx". * This method can be used by subclasses to enforce having certain lint * options enabled/disabled. * * @throws IllegalArgumentException if the option name is not recognized * via the {@link SupportedLintOptions} annotation or the {@link * SourceChecker#getSupportedLintOptions} method * @param name the name of the lint option to set * @param val the option value * * @see SourceChecker#getLintOption(String) * @see SourceChecker#getLintOption(String,boolean) */ protected final void setLintOption(String name, boolean val) { if (!this.getSupportedLintOptions().contains(name)) { ErrorReporter.errorAbort("Illegal lint option: " + name); } /* TODO: warn if the option is also provided on the command line(?) boolean exists = false; if (!activeLints.isEmpty()) { String tofind = name; while (tofind != null) { if (activeLints.contains(tofind) || // direct activeLints.contains(String.format("-%s", tofind)) || // negation activeLints.contains(tofind.substring(1))) { // name was negation exists = true; } tofind = parentOfOption(tofind); } } if (exists) { // TODO: Issue warning? } TODO: assert that name doesn't start with '-' */ Set newlints = new HashSet(); newlints.addAll(activeLints); if (val) { newlints.add(name); } else { newlints.add(String.format("-%s", name)); } activeLints = Collections.unmodifiableSet(newlints); } /** * Helper method to find the parent of a lint key. The lint hierarchy * level is donated by a colon ':'. 'all' is the root for all hierarchy. * *
     * Example
     *    cast:unsafe → cast
     *    cast        → all
     *    all         → {@code null}
     * 
*/ private String parentOfOption(String name) { if (name.equals("all")) { return null; } else if (name.contains(":")) { return name.substring(0, name.lastIndexOf(':')); } else { return "all"; } } /** * Returns the lint options recognized by this checker. Lint options are * those which can be checked for via {@link SourceChecker#getLintOption}. * * @return an unmodifiable {@link Set} of the lint options recognized by * this checker */ public Set getSupportedLintOptions() { if (supportedLints == null) { supportedLints = createSupportedLintOptions(); } return supportedLints; } /** * Compute the set of supported lint options. */ protected Set createSupportedLintOptions() { /*@Nullable*/ SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); if (sl == null) { return Collections.emptySet(); } /*@Nullable*/ String /*@Nullable*/ [] slValue = sl.value(); assert slValue != null; /*nninvariant*/ /*@Nullable*/ String [] lintArray = slValue; Set lintSet = new HashSet(lintArray.length); for (String s : lintArray) { lintSet.add(s); } return Collections.unmodifiableSet(lintSet); } /** * Set the supported lint options. * Use of this method should be limited to the AggregateChecker, * who needs to set the lint options to the union of all subcheckers. * Also, e.g. the NullnessSubchecker/RawnessSubchecker need to * use this method, as one is created by the other. */ protected void setSupportedLintOptions(Set newlints) { supportedLints = newlints; } /** * Add additional active options. * Use of this method should be limited to the AggregateChecker, * who needs to set the active options to the union of all subcheckers. */ protected void addOptions(Map moreopts) { Map activeOpts = new HashMap(getOptions()); activeOpts.putAll(moreopts); activeOptions = Collections.unmodifiableMap(activeOpts); } /** * Determines the value of the option with the given name. * * @see SourceChecker#getLintOption(String,boolean) */ @Override public final String getOption(String name) { return getOption(name, null); } /** * Return all active options for this checker. * @return all active options for this checker */ @Override public Map getOptions() { if (activeOptions == null) { activeOptions = createActiveOptions(processingEnv.getOptions()); } return activeOptions; } /** * Check whether the given option is provided. * Note that {@link #getOption(String)} can still return null even * if hasOption is true: this happens e.g. for -Amyopt * * @param name the option name to check * @return true if the option name was provided, false otherwise */ // TODO I would like to rename getLintOption to hasLintOption @Override public final boolean hasOption(String name) { return getOptions().containsKey(name); } /** * Determines the value of the lint option with the given name and * returns the default value if nothing is specified. * * @see SourceChecker#getOption(String) * @see SourceChecker#getLintOption(String) */ @Override public final String getOption(String name, String def) { if (!this.getSupportedOptions().contains(name)) { ErrorReporter.errorAbort("Illegal option: " + name); } if (activeOptions == null) { activeOptions = createActiveOptions(processingEnv.getOptions()); } if (activeOptions.isEmpty()) { return def; } if (activeOptions.containsKey(name)) { return activeOptions.get(name); } else { return def; } } /** * Map the Checker Framework version of {@link SupportedOptions} to * the standard annotation provided version * {@link javax.annotation.processing.SupportedOptions}. */ @Override public Set getSupportedOptions() { Set options = new HashSet(); // Support all options provided with the standard // {@link javax.annotation.processing.SupportedOptions} // annotation. options.addAll(super.getSupportedOptions()); // For the Checker Framework annotation // {@link org.checkerframework.framework.source.SupportedOptions} // we additionally add Class clazz = this.getClass(); List> clazzPrefixes = new LinkedList<>(); do { clazzPrefixes.add(clazz); SupportedOptions so = clazz.getAnnotation(SupportedOptions.class); if (so != null) { options.addAll(expandCFOptions(clazzPrefixes, so.value())); } clazz = clazz.getSuperclass(); } while (clazz != null && !clazz.getName().equals(AbstractTypeProcessor.class.getCanonicalName())); return Collections.unmodifiableSet(options); } /** * Generate the possible command-line option names by prefixing * each class name from {@code classPrefixes} to {@code options}, * separated by OPTION_SEPARATOR. * * @param clazzPrefixes the classes to prefix * @param options the option names * @return the possible combinations that should be supported */ protected Collection expandCFOptions( List> clazzPrefixes, String[] options) { Set res = new HashSet<>(); for (String option : options) { res.add(option); for (Class clazz : clazzPrefixes) { res.add(clazz.getCanonicalName() + OPTION_SEPARATOR + option); res.add(clazz.getSimpleName() + OPTION_SEPARATOR + option); } } return res; } /** * Overrides the default implementation to always * return a singleton set containing only "*". *

* * javac uses this list to determine which classes process; javac only * runs an annotation processor on classes that contain at least one of * the mentioned annotations. * Thus, the effect of returning "*" * is as if the checker were annotated by * {@code @SupportedAnnotationTypes("*")}: * javac runs the checker on every * class mentioned on the javac command line. This method also checks * that subclasses do not contain a {@link SupportedAnnotationTypes} * annotation.

* * To specify the annotations that a checker recognizes as type qualifiers, * see {@link AnnotatedTypeFactory#createSupportedTypeQualifiers()}. * * @throws Error if a subclass is annotated with * {@link SupportedAnnotationTypes} */ @Override public final Set getSupportedAnnotationTypes() { SupportedAnnotationTypes supported = this.getClass().getAnnotation( SupportedAnnotationTypes.class); if (supported != null) { ErrorReporter.errorAbort("@SupportedAnnotationTypes should not be written on any checker;" + " supported annotation types are inherited from SourceChecker."); } return Collections.singleton("*"); } /** * @return string keys that a checker honors for suppressing warnings * and errors that it issues. Each such key suppresses all * warnings issued by the checker. * * @see SuppressWarningsKeys */ public Collection getSuppressWarningsKeys() { return getStandardSuppressWarningsKeys(); } /** * Determine the standard set of suppress warning keys usable for any checker. * * @see #getSuppressWarningsKeys() * @return collection of warning keys */ protected final Collection getStandardSuppressWarningsKeys() { SuppressWarningsKeys annotation = this.getClass().getAnnotation(SuppressWarningsKeys.class); Set result = new HashSet<>(); result.add(SUPPRESS_ALL_KEY); if (annotation != null) { // Add from annotation for (String key : annotation.value()) { result.add(key); } } else { // No annotation, by default infer key from class name String className = this.getClass().getSimpleName(); int indexOfChecker = className.lastIndexOf("Checker"); if (indexOfChecker == -1) { indexOfChecker = className.lastIndexOf("Subchecker"); } String key = (indexOfChecker == -1) ? className : className.substring(0, indexOfChecker); result.add(key.trim().toLowerCase()); } return result; } /** * Tests whether the class owner of the passed element is an unannotated * class and matches the pattern specified in the * {@code checker.skipUses} property. * * @param element an element * @return true iff the enclosing class of element should be skipped */ public final boolean shouldSkipUses(Element element) { if (element == null) { return false; } TypeElement typeElement = ElementUtils.enclosingClass(element); String name = typeElement.toString(); return shouldSkipUses(name); } /** * Tests whether the class owner of the passed type matches * the pattern specified in the {@code checker.skipUses} property. * In contrast to {@link #shouldSkipUses(Element)} this version * can also be used from primitive types, which don't have an element. * * @param typeName the fully-qualified name of a type * @return true iff the enclosing class of element should be skipped */ public final boolean shouldSkipUses(String typeName) { // System.out.printf("shouldSkipUses(%s) %s%nskipUses %s%nonlyUses %s%nresult %s%n", // element, // name, // skipUsesPattern.matcher(name).find(), // onlyUsesPattern.matcher(name).find(), // (skipUsesPattern.matcher(name).find() // || ! onlyUsesPattern.matcher(name).find())); // StackTraceElement[] stea = new Throwable().getStackTrace(); // for (int i=0; i<3; i++) { // System.out.println(" " + stea[i]); // } // System.out.println(); if (skipUsesPattern == null) { skipUsesPattern = getSkipUsesPattern(getOptions()); } if (onlyUsesPattern == null) { onlyUsesPattern = getOnlyUsesPattern(getOptions()); } return skipUsesPattern.matcher(typeName).find() || ! onlyUsesPattern.matcher(typeName).find(); } /** * Tests whether the class definition should not be checked because it * matches the {@code checker.skipDefs} property. * * @param node class to potentially skip * @return true if checker should not test node */ public final boolean shouldSkipDefs(ClassTree node) { String qualifiedName = InternalUtils.typeOf(node).toString(); // System.out.printf("shouldSkipDefs(%s) %s%nskipDefs %s%nonlyDefs %s%nresult %s%n%n", // node, // qualifiedName, // skipDefsPattern.matcher(qualifiedName).find(), // onlyDefsPattern.matcher(qualifiedName).find(), // (skipDefsPattern.matcher(qualifiedName).find() // || ! onlyDefsPattern.matcher(qualifiedName).find())); if (skipDefsPattern == null) { skipDefsPattern = getSkipDefsPattern(getOptions()); } if (onlyDefsPattern == null) { onlyDefsPattern = getOnlyDefsPattern(getOptions()); } return skipDefsPattern.matcher(qualifiedName).find() || ! onlyDefsPattern.matcher(qualifiedName).find(); } /** * Tests whether the method definition should not be checked because it * matches the {@code checker.skipDefs} property. * * TODO: currently only uses the class definition. Refine pattern. Same for skipUses. * * @param cls class to potentially skip * @param meth method to potentially skip * @return true if checker should not test node */ public final boolean shouldSkipDefs(ClassTree cls, MethodTree meth) { return shouldSkipDefs(cls); } /** * A helper function to parse a Properties file * * @param cls the class whose location is the base of the file path * @param filePath the name/path of the file to be read * @return the properties */ protected Properties getProperties(Class cls, String filePath) { Properties prop = new Properties(); try { InputStream base = cls.getResourceAsStream(filePath); if (base == null) { // No message customization file was given return prop; } prop.load(base); } catch (IOException e) { message(Kind.WARNING, "Couldn't parse properties file: " + filePath); // e.printStackTrace(); // ignore the possible customization file } return prop; } @Override public final SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy