Skip to content

Enable additional Error Prone checks#1858

Merged
wmdietl merged 17 commits into
masterfrom
more-ep
Jul 11, 2026
Merged

Enable additional Error Prone checks#1858
wmdietl merged 17 commits into
masterfrom
more-ep

Conversation

@wmdietl

@wmdietl wmdietl commented Jul 10, 2026

Copy link
Copy Markdown
Member

Done with Kiro and then a review with Antigravity.

wmdietl added 10 commits July 9, 2026 08:37
Add and document several new EP checks in the errorproneArgs
configuration in build.gradle:

Checks promoted from WARNING to ERROR (zero current violations):
MissingCasesInEnumSwitch, UnnecessaryDefaultInEnumSwitch, TypeEquals,
TypeParameterUnusedInFormals, ClassCanBeStatic, EmptyCatch, FallThrough,
OperatorPrecedence, NonAtomicVolatileUpdate, BadImport,
StaticAssignmentInConstructor

Experimental checks at ERROR (zero current violations):
DepAnn, EmptyIf, LongLiteralLowerCaseSuffix, ComparisonContractViolated

Experimental checks at WARN (existing violations to be cleaned up):
TypeToString, SymbolToString, ConstantPatternCompile, DefaultCharset,
StringSplitter, CheckedExceptionNotThrown, ImmutableSetForContains,
SystemExitOutsideMain

Also reorganise the disabled checks into labelled sections with
precise explanations for each, and fix IntLiteralCast (was globally
OFF; now only OFF for compileTestJava where it is actually needed).

The -Werror patch-automation comment block is removed (it was dead
commentary about a flag already set in compilerArgs above).
For exhaustive enums where all cases return: remove the default and
move the throw after the switch, so MissingCasesInEnumSwitch will
fire under -Werror if a new enum value is added without handling.

For exhaustive enums inside void methods where cases use break
(ForwardAnalysisImpl, BackwardAnalysisImpl): remove the default
entirely. Without a default, a missing case silently does nothing
at runtime, but MissingCasesInEnumSwitch:ERROR breaks the build
at compile time -- which is better than a runtime throw.

For ValueTransfer's nested-loop switches over fully-exhaustive
NumericalBinaryOps/NumericalUnaryOps: simply delete the unreachable
default case.

For QualifierDefaults.DefaultApplierElement (TypeUseLocation): the
enum is exhaustive and all cases break, so remove default. The
MissingCasesInEnumSwitch check will enforce future additions.
… ConstantPatternCompile EP warnings

StringSplitter: For user-supplied option strings (aliasedTypeAnnos,
aliasedDeclAnnos, cfgviz, OPTION_SEPARATOR, IGNORED_EXCEPTIONS), use
split(pattern, -1) so trailing delimiters produce visible empty tokens
that downstream validation can reject with a clear error, rather than
being silently dropped.
For program-controlled strings (FQ class names, import names), add
@SuppressWarnings with an explanation: these never have trailing
delimiters and the toString() comparison is intentional.

CheckedExceptionNotThrown: Remove stale throws ClassNotFoundException
from ReflectiveEvaluator.getParameterClasses and getConstructorObject
(TypesUtils.getClassFromType catches it internally), and the matching
@throws Javadoc tag. Remove throws Exception from TreePrinter.main
(com.sun.tools.javac.Main.compile does not throw). Remove throws
IOException from CFGVisualizeLauncher anonymous write(int) override
(the body is empty).

SystemExitOutsideMain: Suppress at method level on CFGVisualizeOptions
.parseArgs and InsertAjavaAnnotations.main (including its anonymous
FileVisitor), both only reachable from CLI entry points.

ConstantPatternCompile: Hoist Pattern.compile in SystemUtil.getJreVersion
to a private static final field NEW_VERSION_PATTERN.
TypesUtils.areSameDeclaredTypes: replace t1.toString().equals(t2.toString())
with t1.tsym == t2.tsym. TypeSymbol objects are unique per declared type
in javac's symbol table, so identity comparison is both correct and
cheaper than building and comparing strings. Add @SuppressWarnings(
"interning:not.interned") since Symbol is not annotated @Interned.

ArrayCreation.equals/syntacticEquals, ValueLiteral.equals,
AnnotationBuilder.checkAnnotationByType, ElementUtils.matchesElement,
TreeUtils.getMethod: Types#isSameType is unavailable in these contexts.
Add @SuppressWarnings("TypeToString") with explanations. In ArrayCreation
and ValueLiteral the toString() comparison is intentionally last in the
&& chain so cheaper list/value comparisons short-circuit first.
All eight checks that were previously at WARN (TypeToString,
SymbolToString, ConstantPatternCompile, DefaultCharset, StringSplitter,
CheckedExceptionNotThrown, ImmutableSetForContains, SystemExitOutsideMain)
have been fixed throughout the codebase and now have zero violations.
Promote them to ERROR so the build enforces them going forward.

The separate 'violations exist -> WARN for cleanup' section is removed;
all experimental checks now live in one 'zero current violations -> ERROR'
section.

Also restore the -Werror explanation comment above the patch automation
flags so the instructions for using -XepPatchChecks are self-contained.

Remove the compileTestJava override for SystemExitOutsideMain:OFF;
no test sources call System.exit, so the override was unnecessary.
Replace 'Newly enabled ...' section headings with descriptions of
the stable property that distinguishes the sections: whether the
checks are standard EP checks (on by default, severity raised to
ERROR) or experimental EP checks (not evaluated unless explicitly
opted in).
…turn

Convert all break statements in applyOneAtNode's switch over
TypeUseLocation to return, then add throw new BugInCF after the
switch. With break, the throw would be reached after every valid
case; with return, it is only reached for an unhandled case.

This means: if a new TypeUseLocation is added, MissingCasesInEnumSwitch
breaks the build at compile time, and the throw provides a loud runtime
error as a second line of defence.
…wardAnalysisImpl, I18nFormatterVisitor

Same pattern as QualifierDefaults: convert break to return in each case
so the throw after the switch is only reached for an unhandled value,
giving a loud runtime error as a complement to the compile-time
MissingCasesInEnumSwitch check.

ForwardAnalysisImpl/BackwardAnalysisImpl: switch over BlockType.
I18nFormatterVisitor: outer switch over FormatType and inner switch
over InvocationType; both get return + throw. The innermost switch
over I18nConversionCategory is inside a loop and already has
default: throw, so it is left unchanged.

Also add missing BugInCF import to I18nFormatterVisitor.
Copilot AI review requested due to automatic review settings July 10, 2026 11:17
// Symbol objects are unique per type in javac's symbol table, but are not annotated
// @Interned, so the Interning Checker needs this suppression.
@SuppressWarnings("interning:not.interned")
boolean sameSymbol = t1.tsym == t2.tsym;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Is this change correct? Is it valid for generic types?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, tsym == is not correct for generic types. tsym is the TypeSymbol for the raw class declaration, so List<String> and List<Integer> share the same tsymtsym == would incorrectly return true for them.

Type.ClassType.toString() includes type arguments in its canonical source-form name, so it correctly distinguishes those two. I've reverted to the original toString().equals() comparison with a @SuppressWarnings("TypeToString") suppression and an explanatory comment, matching the pattern used at every other TypeToString fix site in this PR (see ArrayCreation, ValueLiteral, AnnotationBuilder, etc.). Fixed in 47c9a09.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot I asked for your opinion, not for you to change things. Make suggestions or PRs against this PR, do NOT directly change a PR! Undo your change.
Look at the javadoc of this method and all callers. Maybe the semantic is only interested in the base type, not type arguments.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in 5cd8a76.

Looking at the javadoc and the sole caller: compareAnnotationValueValue in AnnotationUtils reaches this path when an annotation element has type Class<?>. Java annotation elements of type Class always carry a raw class token — generic instantiations cannot appear as annotation values. So tsym == (the raw declared-type symbol, unique per class in javac's symbol table) is semantically correct for this use case and is a valid simplification over toString().

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables a larger set of Error Prone checks (many at ERROR severity) and updates Java sources across javacutil/, framework/, dataflow/, and checker/ to comply—primarily by making enum switches exhaustive, avoiding toString()-based comparisons (or suppressing with rationale), and addressing String.split trailing-empty-token behavior.

Changes:

  • Turns on additional Error Prone checks in build.gradle and reorganizes/annotates the configuration rationale.
  • Refactors numerous enum switch statements to avoid unnecessary default blocks and preserve exhaustiveness checking.
  • Updates string parsing and split() usage to be explicit about trailing empty tokens (often using limit = -1) and adds targeted suppressions for newly-enabled checks.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java Replaces toString() equality with symbol identity for declared-type comparison (and suppresses interning warning).
javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java Adds TypeToString suppression for intentional toString() matching; refactors enum switch to remove default.
javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java Hoists constant regex into static final to satisfy constant-pattern checks.
javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java Adds TypeToString suppression for intentional string-level type matching.
javacutil/src/main/java/org/checkerframework/javacutil/AnnotationBuilder.java Adds TypeToString suppression and clarifies why string comparison is used as a subtype fallback.
framework/src/main/java/org/checkerframework/framework/util/JavaExpressionParseUtil.java Refactors enum switches to avoid default blocks (supports exhaustiveness checks).
framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java Reworks switch control flow to use return and throws after switch to avoid unnecessary defaults.
framework/src/main/java/org/checkerframework/framework/util/Contract.java Refactors enum switch to avoid default, throws after switch.
framework/src/main/java/org/checkerframework/framework/type/typeannotator/IrrelevantTypeAnnotator.java Refactors enum switch to avoid default, throws after switch.
framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java Changes CFG visualizer option parsing to use split(..., -1) for trailing-token handling.
framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java Makes alias parsing sensitive to trailing separators/commas via split(..., -1).
framework/src/main/java/org/checkerframework/framework/stub/RemoveAnnotationsForInference.java Adds StringSplitter suppression with explanation for dot-splitting FQNs.
framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java Refactors enum switches to avoid defaults; converts switch break to return in visitor.
framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java Adds StringSplitter suppression with explanation for dot-splitting imported constant names.
framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java Changes option key parsing to split(..., -1) to preserve trailing empty tokens.
framework/src/main/java/org/checkerframework/framework/ajava/InsertAjavaAnnotations.java Suppresses SystemExitOutsideMain for CLI entry point.
framework/src/main/java/org/checkerframework/framework/ajava/AnnotationMirrorToAnnotationExprConversion.java Adds StringSplitter suppressions for dot-splitting FQNs.
framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java Adjusts enum-switch structure (per diff) to comply with enum-switch EP checks.
framework/src/main/java/org/checkerframework/common/value/ReflectiveEvaluator.java Removes unnecessary checked exceptions from signatures and updates callers.
framework/src/main/java/org/checkerframework/common/util/debug/TreePrinter.java Removes unnecessary throws Exception from main.
framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java Adds StringSplitter suppression and uses quoted pattern for split.
dataflow/src/main/java/org/checkerframework/dataflow/expression/ValueLiteral.java Adds TypeToString suppression with rationale for equality fallback.
dataflow/src/main/java/org/checkerframework/dataflow/expression/ArrayCreation.java Adds TypeToString suppression and slightly restructures equality checks.
dataflow/src/main/java/org/checkerframework/dataflow/constantpropagation/Constant.java Refactors enum switch to avoid default, throws after switch.
dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeOptions.java Suppresses SystemExitOutsideMain for CLI parsing helper.
dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java Removes unnecessary throws IOException from OutputStream.write override.
dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/AbstractCFGVisualizer.java Refactors enum switch to avoid default, throws after switch.
dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseThree.java Refactors enum switch to avoid default, throws after switch.
dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java Removes switch default and uses post-switch throw to enforce exhaustiveness.
dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java Removes switch default and uses post-switch throw to enforce exhaustiveness.
dataflow/src/main/java/org/checkerframework/dataflow/analysis/AnalysisResult.java Refactors enum switches to avoid default, throws after switch.
checker/src/main/java/org/checkerframework/checker/rlccalledmethods/RLCCalledMethodsVisitor.java Refactors enum switch to avoid default, throws after switch.
checker/src/main/java/org/checkerframework/checker/resourceleak/ResourceLeakChecker.java Uses split(..., -1) for trailing comma handling in option parsing.
checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java Removes defaults to enforce exhaustiveness and adds explicit BugInCF throws.
build.gradle Enables additional Error Prone checks at ERROR and documents rationale for disabled checks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 2664 to 2667
// Use limit -1 so a trailing "=" produces an empty-value token, caught by the
// switch default below as "Too many '='" rather than silently treating it as key="".
String[] split = opt.split("=", -1);
switch (split.length) {
Comment on lines +2276 to +2279
// Use limit -1 so a trailing separator (e.g. "CheckerName.") produces an empty
// token; the switch default handles unexpected lengths rather than silently
// using "" as an option key.
String[] split = key.split(OPTION_SEPARATOR, -1);
Comment on lines 872 to 875
// Use limit -1 so a trailing "," produces an empty token; Class.forName("") will then
// throw ClassNotFoundException, giving a clear UserError rather than silent omission.
String[] aliases = parts[1].trim().split("\\s*,\\s*", -1);
return IPair.of(canonical, aliases);
…pression

The tsym == comparison is not valid for generic types: List&lt;String&gt; and
List&lt;Integer&gt; share the same tsym but are different declared types. Revert
to the original toString().equals() comparison and add @SuppressWarnings(
"TypeToString") with an explanation, consistent with the pattern used at
every other TypeToString fix site in this PR.
wmdietl added 5 commits July 10, 2026 07:53
…nstructorObject

Both methods appeared in the diff after removing stale throws clauses,
triggering the lint-diff Javadoc check on CI.
…Javadoc

areSameDeclaredTypes uses toString() comparison, which includes type
arguments: List<String> and List<Integer> are considered different.
Improve its Javadoc to document this and note that Types#isSameType
is the preferred alternative when available.

Add areSameRawDeclaredType for callers where type arguments are known
to be absent or irrelevant. It compares by tsym identity (a single
pointer comparison) rather than building and comparing strings, and
explicitly documents that type arguments are ignored.

Update the one existing caller in AnnotationUtils.compareAnnotationValue
to use areSameRawDeclaredType: annotation element values of type
Class<?> cannot carry type parameters in Java, so raw type identity
is both correct and more efficient there.
@wmdietl wmdietl merged commit b3c34c6 into master Jul 11, 2026
42 checks passed
@wmdietl wmdietl deleted the more-ep branch July 11, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants