package org.checkerframework.framework.source; /*>>> import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.*; */ 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; 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.TreeSet; 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 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; /** * 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. * * <p>Subclasses must implement the following methods: (TODO: update the list) * * <ul> * <li>{@link SourceChecker#getMessages} (for type-qualifier specific error messages) * <li>{@link SourceChecker#createSourceVisitor} (for a custom {@link SourceVisitor}) * <li>{@link SourceChecker#getSuppressWarningsKeys} (for honoring {@literal @}{link * SuppressWarnings} annotations) * </ul> * * 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: // * docs/manual/introduction.tex contains a list 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 /// // A comma-separated list of warnings to suppress // org.checkerframework.framework.source.SourceChecker.createSuppressWarnings "suppressWarnings", // Set inclusion/exclusion of type uses or definitions // org.checkerframework.framework.source.SourceChecker.shouldSkipUses and similar "skipUses", "onlyUses", "skipDefs", "onlyDefs", // Whether to ignore all subtype tests for type arguments that // were inferred for a raw type // org.checkerframework.framework.type.TypeHierarchy.isSubtypeTypeArguments "ignoreRawTypeArguments", // Unsoundly ignore side effects "assumeSideEffectFree", // Whether to assume that assertions are enabled or disabled // org.checkerframework.framework.flow.CFCFGBuilder.CFCFGBuilder "assumeAssertionsAreEnabled", "assumeAssertionsAreDisabled", // Treat checker errors as warnings // org.checkerframework.framework.source.SourceChecker.report "warns", /// /// More sound (strict checking): enable errors that are disabled by default /// // The next ones *increase* rather than *decrease* soundness. // They will eventually be replaced by their complements // (except -AconcurrentSemantics) and moved into the above section. // 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 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 assume sound concurrent semantics or // simplified sequential semantics // org.checkerframework.framework.flow.CFAbstractTransfer.sequentialSemantics "concurrentSemantics", // Whether to use a conservative value for type arguments that could not be inferred. // See Issue 979. "conservativeUninferredTypeArguments", /// /// 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 resolve reflective method invocations. // "-AresolveReflection=debug" causes debugging information // to be output. "resolveReflection", // Whether to use .jaif files whole-program inference "infer", // With each warning, in addition to the concrete error key, // output the suppress warning keys that can be used to // suppress that warning. "showSuppressWarningKeys", /// /// Partially-annotated 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", // Already listed above, but worth noting again in this section: // "useDefaultsForUncheckedCode" /// /// Debugging /// /// Amount of detail in messages // Whether to print @InvisibleQualifier marked annotations // org.checkerframework.framework.type.AnnotatedTypeMirror.toString() "printAllQualifiers", // Whether to print [] around a set of type parameters in order to clearly see where they end // e.g. <E extends F, F extends Object> // 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", /// Visualizing the CFG // Implemented in the wrapper rather than this file, but worth noting here. // -AoutputArgsToFile // 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", /// Miscellaneous debugging options // 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. * * <p>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. * * <p>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. * * <p>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. * * <p>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<String> supportedLints; /** The enabled lint options */ private Set<String> 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. * * <p>Both the simple and the canonical name of the checker can be used. Superclasses of the * current checker are also considered. */ private Map<String, String> 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 static final String OPTION_SEPARATOR = "_"; /** The line separator */ private static final 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<String> upstreamCheckerNames = null; @Override public final synchronized 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<String> getUpstreamCheckerNames() { if (upstreamCheckerNames == null) { upstreamCheckerNames = new ArrayList<String>(); 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. * * <p>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<Class<?>> checkers = new Stack<Class<?>>(); 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<String, String> 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<String, String> options) { // default matches everything return getPattern(patternName, options, "."); } private Pattern getPattern( String patternName, Map<String, String> 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<String, String> options) { return getSkipPattern("skipUses", options); } private Pattern getOnlyUsesPattern(Map<String, String> options) { return getOnlyPattern("onlyUses", options); } private Pattern getSkipDefsPattern(Map<String, String> options) { return getSkipPattern("skipDefs", options); } private Pattern getOnlyDefsPattern(Map<String, String> options) { return getOnlyPattern("onlyDefs", options); } // TODO: do we want this? // Cache the keys that we already warned about to prevent repetitions. // private Set<String> warnedOnLint = new HashSet<String>(); private Set<String> createActiveLints(Map<String, String> options) { if (!options.containsKey("lint")) { return Collections.emptySet(); } String lintString = options.get("lint"); if (lintString == null) { return Collections.singleton("all"); } Set<String> activeLint = new HashSet<String>(); 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<String, String> createActiveOptions(Map<String, String> options) { if (options.isEmpty()) { return Collections.emptyMap(); } Map<String, String> activeOpts = new HashMap<String, String>(); for (Map.Entry<String, String> 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<String, String> 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. * * <p>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} * * <p>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. 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<MemoryPoolMXBean> 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) { printMessage(kind, messageText, (Tree) source, currentRoot); } else { ErrorReporter.errorAbort("invalid position source: " + source.getClass().getName()); } } /** * Do not call this method directly. Call {@link #message(Kind, Object, String, Object...)} * instead. (This method exists so that the BaseTypeChecker can override it and treat messages * from compound checkers differently.) */ protected void printMessage( Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { Trees.instance(processingEnv).printMessage(kind, message, source, root); } /** * 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 * {@code -AsuppressWarnings} command-line argument. * * <p>A @SuppressWarnings value may be of the following pattern: * * <ol> * <li>{@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) * <li>{@code "suppress-key:error-key}, where the suppress-key is as above, and error-key is a * prefix or suffix of the errors that it may suppress. So "nullness:generic.argument", * would suppress any errors in the Nullness Checker related to generic.argument. * </ol> * * @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<String> 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<String> 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<String> 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<String> 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 <a * href="https://docs.oracle.com/javase/7/docs/technotes/guides/javac/index.html">javac</a> 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 <a * href="https://docs.oracle.com/javase/1.5.0/docs/tooldocs/solaris/javac.html">javac</a> 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 <a * href="https://docs.oracle.com/javase/1.5.0/docs/tooldocs/solaris/javac.html">javac</a> 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<String> newlints = new HashSet<String>(); 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. * * <pre> * Example * cast:unsafe → cast * cast → all * all → {@code null} * </pre> */ 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<String> getSupportedLintOptions() { if (supportedLints == null) { supportedLints = createSupportedLintOptions(); } return supportedLints; } /** Compute the set of supported lint options. */ protected Set<String> createSupportedLintOptions() { /*@Nullable*/ SupportedLintOptions sl = this.getClass().getAnnotation(SupportedLintOptions.class); if (sl == null) { return Collections.</*@NonNull*/ String>emptySet(); } /*@Nullable*/ String /*@Nullable*/ [] slValue = sl.value(); assert slValue != null; /*nninvariant*/ /*@Nullable*/ String[] lintArray = slValue; Set<String> lintSet = new HashSet<String>(lintArray.length); for (String s : lintArray) { lintSet.add(s); } return Collections.</*@NonNull*/ String>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<String> 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<String, String> moreopts) { Map<String, String> activeOpts = new HashMap<String, String>(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<String, String> getOptions() { if (activeOptions == null) { activeOptions = createActiveOptions(processingEnv.getOptions()); } return activeOptions; } /** * Check whether the given option is provided. * * <p>Note that {@link #getOption(String)} can still return null even if {@code hasOption} * returns true: this happens e.g. for {@code -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<String> getSupportedOptions() { Set<String> options = new HashSet<String>(); // 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<Class<?>> 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.</*@NonNull*/ String>unmodifiableSet(options); } /** * Generate the possible command-line option names by prefixing each class name from {@code * classPrefixes} to {@code options}, separated by {@code OPTION_SEPARATOR}. * * @param clazzPrefixes the classes to prefix * @param options the option names * @return the possible combinations that should be supported */ protected Collection<String> expandCFOptions( List<? extends Class<?>> clazzPrefixes, String[] options) { Set<String> 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 "*". * * <p>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. * * <p>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<String> 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<String> 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<String> getStandardSuppressWarningsKeys() { SuppressWarningsKeys annotation = this.getClass().getAnnotation(SuppressWarningsKeys.class); // TreeSet ensures keys are returned in a consistent order. Set<String> result = new TreeSet<>(); 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. * * <p>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(); } }