From 8e1bd105c37dace3189954547f44f17464d5bf06 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Thu, 18 Jul 2024 17:26:07 -0700 Subject: [PATCH 01/39] Treat all `com.google.protobuf.ProtocolMessageEnum`s as immutable PiperOrigin-RevId: 653806506 --- .../bugpatterns/threadsafety/ThreadSafety.java | 11 +++++------ .../threadsafety/WellKnownMutability.java | 8 ++++++++ .../threadsafety/ThreadSafeCheckerTest.java | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java index 7379634fe6d..693d756acca 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java @@ -367,16 +367,12 @@ public static Violation create(ConsPStack path) { return new AutoValue_ThreadSafety_Violation(path); } - /** - * @return true if a violation was found - */ + /** Returns true if a violation was found. */ public boolean isPresent() { return !path().isEmpty(); } - /** - * @return the explanation - */ + /** Returns the explanation. */ public String message() { return Joiner.on(", ").join(path()); } @@ -658,6 +654,9 @@ public Violation visitType(Type type, Void s) { } return Violation.absent(); } + if (WellKnownMutability.isProtoEnum(state, type)) { + return Violation.absent(); + } return Violation.of( String.format( "the declaration of type '%s' is not annotated with %s", diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownMutability.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownMutability.java index 15d135620db..796758ebce9 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownMutability.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownMutability.java @@ -342,6 +342,9 @@ private static ImmutableSet buildMutableClasses(List knownMutabl private static final Supplier PROTOCOL_MESSAGE_TYPE = Suppliers.typeFromString("com.google.io.protocol.ProtocolMessage"); + private static final Supplier PROTOCOL_MESSAGE_ENUM = + Suppliers.typeFromString("com.google.protobuf.ProtocolMessageEnum"); + private static boolean isAssignableTo(Type type, Supplier supplier, VisitorState state) { Type to = supplier.get(state); if (to == null) { @@ -371,6 +374,11 @@ public static boolean isProto2MutableMessageClass(VisitorState state, Type type) && !isAssignableTo(type, PROTOCOL_MESSAGE_TYPE, state); } + public static boolean isProtoEnum(VisitorState state, Type type) { + checkNotNull(type); + return isAssignableTo(type, PROTOCOL_MESSAGE_ENUM, state); + } + /** Returns true if the type is an annotation. */ public static boolean isAnnotation(VisitorState state, Type type) { return isAssignableTo(type, Suppliers.ANNOTATION_TYPE, state); diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeCheckerTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeCheckerTest.java index cd682d46480..3a186843e69 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeCheckerTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeCheckerTest.java @@ -1084,4 +1084,21 @@ public void threadSafeRecursiveUpperBound_notThreadsafe() { "}") .doTest(); } + + @Test + public void protoEnum() { + compilationHelper + .addSourceLines( + "E.java", + "import com.google.protobuf.ProtocolMessageEnum;", + "abstract class E implements ProtocolMessageEnum {", + "}") + .addSourceLines( + "Test.java", + "import com.google.errorprone.annotations.ThreadSafe;", + "@ThreadSafe class Test {", + " final E x = null;", + "}") + .doTest(); + } } From 053f721836d3e88dbefb0fbe478300224707797b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 19 Jul 2024 15:11:51 -0700 Subject: [PATCH 02/39] Add `TraditionalSwitchExpression.md`. PiperOrigin-RevId: 654138846 --- docs/bugpattern/TraditionalSwitchExpression.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 docs/bugpattern/TraditionalSwitchExpression.md diff --git a/docs/bugpattern/TraditionalSwitchExpression.md b/docs/bugpattern/TraditionalSwitchExpression.md new file mode 100644 index 00000000000..696272f3ba0 --- /dev/null +++ b/docs/bugpattern/TraditionalSwitchExpression.md @@ -0,0 +1,6 @@ +The newer arrow (`->`) syntax for switches is preferred to the older colon (`:`) +syntax. The main reason for continuing to use the colon syntax in switch +*statements* is that it allows fall-through from one statement group to the +next. But in a switch *expression*, fall-through would only be useful if the +code that falls through has side effects. Burying side effects inside a switch +expression makes code hard to understand. From df5eec2c6175a39c383b0409214355838bef2d95 Mon Sep 17 00:00:00 2001 From: markbrady Date: Mon, 22 Jul 2024 07:49:18 -0700 Subject: [PATCH 03/39] [Test-only change] Use TEXT_MATCH mode for StatementSwitchToExpressionSwitch direct conversion unit tests involving comment processing PiperOrigin-RevId: 654746861 --- ...StatementSwitchToExpressionSwitchTest.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index 08f4a892626..e1c4bf949f6 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -118,7 +118,7 @@ public void switchByEnum_removesRedundantBreak_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -196,7 +196,7 @@ public void switchByEnumWithCompletionAnalsis_removesRedundantBreak_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -282,7 +282,7 @@ public void switchByEnumCard_combinesCaseComments_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -328,6 +328,7 @@ public void switchByEnumCard2_removesRedundantBreaks_error() { " }", " ", " public void foo(Side side) { ", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", " switch(side) {", " case HEART:", " System.out.println(\"heart\");", @@ -367,7 +368,7 @@ public void switchByEnumCard2_removesRedundantBreaks_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -604,7 +605,7 @@ public void switchWithDefaultInMiddle_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1155,7 +1156,7 @@ public void switchByEnum_exampleInDocumentation_error() { " private void bar() {}", "}") .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1236,7 +1237,7 @@ public void switchByEnum_caseHasOnlyComments_error() { " private void bar() {}", "}") .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1298,12 +1299,13 @@ public void switchByEnum_accumulatedComments_error() { " enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};", " public Test() {}", " private void foo(Suit suit) {", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", " switch(suit) {", " case HEARTS, DIAMONDS, SPADES, CLUBS -> {", " /* red */", " // A comment here", - " /* red */", " // more comments.", + " /* red */", " // Diamonds comment", " /* black */", " // Spades comment", @@ -1316,7 +1318,7 @@ public void switchByEnum_accumulatedComments_error() { " private void bar() {}", "}") .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1384,7 +1386,6 @@ public void switchByEnum_surroundingBracesCannotRemove_error() { " // The quick brown fox, jumps over the lazy dog, etc.", " break;", " }", - " ", " default -> ", " throw new RuntimeException(\"Invalid type.\");", " ", @@ -1393,7 +1394,7 @@ public void switchByEnum_surroundingBracesCannotRemove_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1466,7 +1467,7 @@ public void switchByEnum_surroundingBracesEmpty_error() { "}") .setArgs( ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1502,8 +1503,7 @@ public void switchByEnum_afterReturnComments_error() { "Test.java", "class Test {", " enum Suit {HEART, SPADE, DIAMOND, CLUB};", - " public Test(int foo) {", - " }", + " public Test(int foo) {}", " ", " public int foo(Suit suit) { ", " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", @@ -1524,7 +1524,9 @@ public void switchByEnum_afterReturnComments_error() { "class Test {", " enum Suit {HEART, SPADE, DIAMOND, CLUB};", " public Test(int foo) {}", + " ", " public int foo(Suit suit) {", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", " switch(suit) {", " case HEART -> {", " // before return comment", @@ -1540,7 +1542,7 @@ public void switchByEnum_afterReturnComments_error() { " }", "}") .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } /********************************** From 94f5eb047e50f73584bf87dc4ea2ab74c5a64945 Mon Sep 17 00:00:00 2001 From: markbrady Date: Mon, 22 Jul 2024 08:26:31 -0700 Subject: [PATCH 04/39] StatementSwitchToExpressionSwitch: Enhance code comment handling for assignment switch and return switch, including retaining comments after the final statement in a case. PiperOrigin-RevId: 654761204 --- .../StatementSwitchToExpressionSwitch.java | 100 +++++-- ...StatementSwitchToExpressionSwitchTest.java | 271 +++++++++++++++++- 2 files changed, 333 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index 11ec6a498be..cf3a3ca05c7 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -509,7 +509,7 @@ private static boolean areStatementsConvertibleToExpressionSwitch( /** * Transforms the supplied statement switch into an expression switch directly. In this * conversion, each nontrivial statement block is mapped one-to-one to a new {@code Expression} or - * {@code StatementBlock} on the right-hand side. Comments are presevered where possible. + * {@code StatementBlock} on the right-hand side. Comments are preserved where possible. */ private static SuggestedFix convertDirectlyToExpressionSwitch( SwitchTree switchTree, VisitorState state, AnalysisResult analysisResult) { @@ -573,9 +573,7 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( } else { // Extract comments (if any) preceding break that was removed as redundant Optional commentsBeforeRemovedBreak = - filteredStatements.isEmpty() - ? Optional.empty() - : extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); + extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); // Join together all comments and code, separating with newlines transformedBlockSource = @@ -638,6 +636,10 @@ private static SuggestedFix convertToReturnSwitch( List statementsToDelete = new ArrayList<>(); List cases = switchTree.getCases(); + ImmutableList allSwitchComments = + state.getTokensForNode(switchTree).stream() + .flatMap(errorProneToken -> errorProneToken.comments().stream()) + .collect(toImmutableList()); StringBuilder replacementCodeBuilder = new StringBuilder(); replacementCodeBuilder .append("return switch ") @@ -663,23 +665,46 @@ private static SuggestedFix convertToReturnSwitch( replacementCodeBuilder.append( isDefaultCase ? "default" : printCaseExpressions(caseTree, state)); + Optional commentsAfterCaseOptional = + extractCommentsAfterCase(switchTree, allSwitchComments, state, caseIndex); if (analysisResult.groupedWithNextCase().get(caseIndex)) { firstCaseInGroup = false; replacementCodeBuilder.append(", "); // Capture comments from this case so they can be added to the group's transformed case if (!transformedBlockSource.trim().isEmpty()) { - groupedCaseCommentsAccumulator.append(removeFallThruLines(transformedBlockSource)); + String commentsToAppend = removeFallThruLines(transformedBlockSource); + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsToAppend); + } + + if (commentsAfterCaseOptional.isPresent()) { + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsAfterCaseOptional.get()); } + // Add additional cases to the list on the lhs of the arrow continue; } else { - // This case is the last case in its group, so insert the collected comments from the lhs of - // the colon here - transformedBlockSource = groupedCaseCommentsAccumulator + transformedBlockSource; + // Join together all comments and code, separating with newlines + transformedBlockSource = + Joiner.on("\n") + .skipNulls() + .join( + // This case is the last case in its group, so insert any comments from prior + // grouped cases first + groupedCaseCommentsAccumulator.length() == 0 + ? null + : groupedCaseCommentsAccumulator.toString(), + transformedBlockSource.isEmpty() ? null : transformedBlockSource, + commentsAfterCaseOptional.orElse(null)); } replacementCodeBuilder.append(" -> "); - // Single statement with no comments - no braces needed - replacementCodeBuilder.append(transformedBlockSource); + // No braces needed + replacementCodeBuilder.append("\n").append(transformedBlockSource); firstCaseInGroup = true; } // case loop @@ -761,6 +786,11 @@ private static SuggestedFix convertToAssignmentSwitch( SwitchTree switchTree, VisitorState state, AnalysisResult analysisResult) { List cases = switchTree.getCases(); + ImmutableList allSwitchComments = + state.getTokensForNode(switchTree).stream() + .flatMap(errorProneToken -> errorProneToken.comments().stream()) + .collect(toImmutableList()); + StringBuilder replacementCodeBuilder = new StringBuilder( state.getSourceForNode( @@ -800,32 +830,52 @@ private static SuggestedFix convertToAssignmentSwitch( replacementCodeBuilder.append( isDefaultCase ? "default" : printCaseExpressions(caseTree, state)); + Optional commentsAfterCaseOptional = + extractCommentsAfterCase(switchTree, allSwitchComments, state, caseIndex); if (analysisResult.groupedWithNextCase().get(caseIndex)) { firstCaseInGroup = false; replacementCodeBuilder.append(", "); // Capture comments from this case so they can be added to the group's transformed case if (!transformedBlockSource.trim().isEmpty()) { - groupedCaseCommentsAccumulator.append(removeFallThruLines(transformedBlockSource)); + String commentsToAppend = removeFallThruLines(transformedBlockSource); + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsToAppend); + } + + if (commentsAfterCaseOptional.isPresent()) { + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsAfterCaseOptional.get()); } // Add additional cases to the list on the lhs of the arrow continue; } else { - // This case is the last case in its group, so insert the collected comments from the lhs of - // the colon here - transformedBlockSource = groupedCaseCommentsAccumulator + transformedBlockSource; + // Extract comments (if any) preceding break that was removed as redundant + Optional commentsBeforeRemovedBreak = + extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); + + // Join together all comments and code, separating with newlines + transformedBlockSource = + Joiner.on("\n") + .skipNulls() + .join( + // This case is the last case in its group, so insert any comments from prior + // grouped cases first + groupedCaseCommentsAccumulator.length() == 0 + ? null + : groupedCaseCommentsAccumulator.toString(), + transformedBlockSource.isEmpty() ? null : transformedBlockSource, + commentsBeforeRemovedBreak.orElse(null), + commentsAfterCaseOptional.orElse(null)); } replacementCodeBuilder.append(" -> "); - // Extract comments (if any) for break that was removed as redundant - Optional commentsBeforeRemovedBreak = - extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); - if (commentsBeforeRemovedBreak.isPresent()) { - transformedBlockSource = transformedBlockSource + "\n" + commentsBeforeRemovedBreak.get(); - } - - // Single statement with no comments - no braces needed - replacementCodeBuilder.append(transformedBlockSource); + // No braces needed + replacementCodeBuilder.append("\n").append(transformedBlockSource); firstCaseInGroup = true; } // case loop @@ -844,8 +894,8 @@ private static Optional extractCommentsBeforeRemovedBreak( CaseTree caseTree, VisitorState state, ImmutableList filteredStatements) { // Was a trailing break removed and some expressions remain? - if (getStatements(caseTree).size() > filteredStatements.size() - && !filteredStatements.isEmpty()) { + if (!filteredStatements.isEmpty() + && getStatements(caseTree).size() > filteredStatements.size()) { // Extract any comments after what is now the last statement and before the removed // break String commentsAfterNewLastStatement = diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index e1c4bf949f6..eab2d16735b 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -215,9 +215,9 @@ public void switchByEnumCard_combinesCaseComments_error() { " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", " switch(side) {", " case HEART:", - " System.out.println(\"heart\");", + " System.out.println(\"heart2\");", " break;", - " case DIAMOND:", + " case /* sparkly */ DIAMOND /* Sparkly */:", " // Empty block comment 1", " // Fall through", " case SPADE:", @@ -247,7 +247,7 @@ public void switchByEnumCard_combinesCaseComments_error() { " case HEART:", " System.out.println(\"heart2\");", " break;", - " case /* sparkly */ DIAMOND:", + " case /* sparkly */ DIAMOND /* Sparkly */:", " // Empty block comment 1", " // Fall through", " case SPADE:", @@ -271,6 +271,7 @@ public void switchByEnumCard_combinesCaseComments_error() { " case HEART -> System.out.println(\"heart2\");", " case DIAMOND, SPADE, CLUB -> {", " /* sparkly */", + " /* Sparkly */", " // Empty block comment 1", " // Empty block comment 2", " // Start of block comment 1", @@ -1679,12 +1680,14 @@ public void switchByEnum_returnSwitchWithShouldNeverHappen_error() { " case SPADE -> throw new RuntimeException();", " case CLUB -> throw new NullPointerException();", " };", + " // This should never happen", + " ", " }", "}") .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1812,7 +1815,7 @@ public void switchByEnum_exhaustiveWithDefault_error() { .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -1915,6 +1918,7 @@ public void switchByEnum_returnSwitchWithShouldNeverHappen_errorAndRemoveShouldN " }", " // Custom comment - should never happen", " int z = invoke(/* block comment 0 */);", + " // Custom comment 2", " {z++;}", " throw new RuntimeException(\"Switch was not exhaustive at runtime \" + z);", " }", @@ -1955,6 +1959,7 @@ public void switchByEnum_returnSwitchWithShouldNeverHappen_errorAndRemoveShouldN " }", " // Custom comment - should never happen", " int z = invoke(/* block comment 0 */);", + " // Custom comment 2", " {z++;}", " throw new RuntimeException(\"Switch was not exhaustive at runtime \" + z);", " }", @@ -1986,6 +1991,9 @@ public void switchByEnum_returnSwitchWithShouldNeverHappen_errorAndRemoveShouldN " case CLUB -> throw new NullPointerException();", " };", " // Custom comment - should never happen", + "", + " // Custom comment 2", + "", " }", " System.out.println(\"don't delete 2\");", " return 0;", @@ -1994,7 +2002,7 @@ public void switchByEnum_returnSwitchWithShouldNeverHappen_errorAndRemoveShouldN .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -2107,7 +2115,136 @@ public void switchByEnum_returnSwitchNoFollowingStatementsInBlock_errorAndNoRemo .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + + @Test + public void switchByEnum_groupedComments_errorAndNoRemoval() { + // The switch is exhaustive but doesn't have any statements immediately following it in the + // lowest ancestor statement block + assumeTrue(RuntimeVersion.isAtLeast14()); + helper + .addSourceLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int invoke() {", + " return 123;", + " }", + " public int foo(Side side) { ", + " System.out.println(\"don't delete 0\");", + " if (invoke() > 0) {", + " System.out.println(\"don't delete 1\");", + " // Preceding comment", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(side) {", + " case HEART /* lhs comment */: // rhs comment", + " // Another comment", + " case /* sparkly */ DIAMOND /* Sparkly */:", + " // Diamond", + " case SPADE:", + " // Before invoke", + " return invoke();", + " // After invoke", + " case CLUB:", + " throw new NullPointerException();", + " // After last case", + " }", + " }", + " // Custom comment - should never happen because invoke returns 123 or throws", + " int z = invoke(/* block comment 0 */);", + " {z++;}", + " throw new RuntimeException(\"Invoke <= 0 at runtime \");", + " }", + "}") + .setArgs( + ImmutableList.of( + "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) .doTest(); + + refactoringHelper + .addInputLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int invoke() {", + " return 123;", + " }", + " public int foo(Side side) { ", + " System.out.println(\"don't delete 0\");", + " if (invoke() > 0) {", + " System.out.println(\"don't delete 1\");", + " // Preceding comment", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(side) {", + " case HEART /* lhs comment */: // rhs comment", + " // Another comment", + " case /* sparkly */ DIAMOND /* Sparkly */:", + " // Diamond", + " case SPADE:", + " // Before invoke", + " return invoke();", + " // After invoke", + " /* More after invoke */", + " case CLUB:", + " throw new NullPointerException();", + " // After last case", + " }", + " }", + " // Custom comment - should never happen because invoke returns 123 or throws", + " int z = invoke(/* block comment 0 */);", + " {z++;}", + " throw new RuntimeException(\"Invoke <= 0 at runtime \");", + " }", + "}") + .addOutputLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int invoke() {", + " return 123;", + " }", + " public int foo(Side side) { ", + " System.out.println(\"don't delete 0\");", + " if (invoke() > 0) {", + " System.out.println(\"don't delete 1\");", + " // Preceding comment", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " return switch(side) {", + " case HEART, DIAMOND, SPADE -> ", + " /* lhs comment */", + " // rhs comment", + " // Another comment", + " /* sparkly */", + " /* Sparkly */", + " // Diamond", + " // Before invoke", + " invoke();", + " // After invoke", + " /* More after invoke */", + " case CLUB -> throw new NullPointerException();", + " // After last case", + " };", + " }", + " // Custom comment - should never happen because invoke returns 123 or throws", + " int z = invoke(/* block comment 0 */);", + " {z++;}", + " throw new RuntimeException(\"Invoke <= 0 at runtime \");", + " }", + "}") + .setArgs( + ImmutableList.of( + "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -2170,6 +2307,7 @@ public void switchByEnum_returnSwitchNoFollowingStatementsInBlock_errorAndNoRemo " case CLUB -> throw new NullPointerException();", " };", " // Custom comment - should never happen", + "", " };", " System.out.println(\"don't delete 2\");", " return lambda.get();", @@ -2178,7 +2316,7 @@ public void switchByEnum_returnSwitchNoFollowingStatementsInBlock_errorAndNoRemo .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -2511,7 +2649,8 @@ public void switchByEnum_assignmentSwitchMixedReferences_error() { " ", " public int foo(Side side) { ", " this.x <<= switch(side) {", - " case HEART -> /* LHS comment */", + " case HEART ->", + " /* LHS comment */", " // Inline comment", " 2;", " case DIAMOND -> (((x+1) * (x*x)) << 1);", @@ -2524,7 +2663,7 @@ public void switchByEnum_assignmentSwitchMixedReferences_error() { .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableAssignmentSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -3080,7 +3219,7 @@ public void switchByEnum_exhaustiveAssignmentSwitch_error() { .setArgs( ImmutableList.of( "-XepOpt:StatementSwitchToExpressionSwitch:EnableAssignmentSwitchConversion")) - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test @@ -3162,6 +3301,112 @@ public void switchByEnum_exhaustiveCompoundAssignmentSwitch_error() { .doTest(); } + @Test + public void switchByEnum_groupedComments_error() { + // Verify compound assignments (here, *=) with grouped comments + assumeTrue(RuntimeVersion.isAtLeast14()); + helper + .addSourceLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int foo(Side side) { ", + " int x = 0;", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(side) {", + " case /* red suit */ HEART:", + " // Heart comment", + " case /* red suit */ DIAMOND: // sparkles", + " // Diamond comment", + " // Fall through", + " case /* black suit */ SPADE:", + " x *= 2;", + " // Before break comment", + " break;", + " // After break comment", + " case /* black suit */ CLUB:", + " // Club comment", + " throw new NullPointerException();", + " // Club after throw comment", + " }", + " return x;", + " }", + "}") + .setArgs( + ImmutableList.of( + "-XepOpt:StatementSwitchToExpressionSwitch:EnableAssignmentSwitchConversion")) + .doTest(); + + refactoringHelper + .addInputLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int foo(Side side) { ", + " int x = 0;", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(side) {", + " case /* red suit */ HEART:", + " // Heart comment", + " case /* red suit */ DIAMOND: // sparkles", + " // Diamond comment", + " // Fall through", + " case /* black suit */ SPADE:", + " x *= 2;", + " // Before break comment", + " break;", + " // After break comment", + " case /* black suit */ CLUB:", + " // Club comment", + " throw new NullPointerException();", + " // Club after throw comment", + " }", + " return x;", + " }", + "}") + .addOutputLines( + "Test.java", + "class Test {", + " enum Side {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int foo(Side side) { ", + " int x = 0;", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " x *= ", + " switch(side) {", + " case HEART, DIAMOND, SPADE ->", + " /* red suit */", + " // Heart comment", + " /* red suit */", + " // sparkles", + " // Diamond comment", + " /* black suit */", + " 2;", + " // Before break comment", + " // After break comment", + " case CLUB ->", + " /* black suit */", + " // Club comment", + " throw new NullPointerException();", + " // Club after throw comment", + " };", + " return x;", + " }", + "}") + .setArgs( + ImmutableList.of( + "-XepOpt:StatementSwitchToExpressionSwitch:EnableAssignmentSwitchConversion")) + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + @Test public void switchByEnum_compoundAssignmentExampleInDocumentation_error() { // This code appears as an example in the documentation (added surrounding class) @@ -3177,7 +3422,7 @@ public void switchByEnum_compoundAssignmentExampleInDocumentation_error() { " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", " switch(suit) {", " case HEARTS:", - " // Fall thru", + " // Fall through", " case DIAMONDS:", " score += -1;", " break;", @@ -3203,7 +3448,7 @@ public void switchByEnum_compoundAssignmentExampleInDocumentation_error() { " private void updateScore(Suit suit) {", " switch(suit) {", " case HEARTS:", - " // Fall thru", + " // Fall through", " case DIAMONDS:", " score += -1;", " break;", @@ -3230,7 +3475,7 @@ public void switchByEnum_compoundAssignmentExampleInDocumentation_error() { " }", "}") .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableAssignmentSwitchConversion") - .doTest(); + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } @Test From dd94f4a5dddfcf2147f4add7bf07838cece21d25 Mon Sep 17 00:00:00 2001 From: cpovirk Date: Mon, 22 Jul 2024 10:03:02 -0700 Subject: [PATCH 05/39] Flag `@OtherNullnessAnnotation void` as we already do `@Nullable void`. We got this wrong with `@CheckForNull` in one file in Guava (unknown commit). PiperOrigin-RevId: 654797514 --- .../errorprone/bugpatterns/NullableVoid.java | 13 +++++++------ .../errorprone/bugpatterns/NullableVoidTest.java | 14 +++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NullableVoid.java b/core/src/main/java/com/google/errorprone/bugpatterns/NullableVoid.java index b65ba237c70..d8ca3cd0b96 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/NullableVoid.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NullableVoid.java @@ -17,6 +17,7 @@ package com.google.errorprone.bugpatterns; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.dataflow.nullnesspropagation.NullnessAnnotations.annotationsRelevantToNullness; import static com.google.errorprone.matchers.Description.NO_MATCH; import com.google.errorprone.BugPattern; @@ -26,7 +27,6 @@ import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; -import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.MethodTree; import com.sun.tools.javac.code.Symbol.MethodSymbol; import javax.lang.model.type.TypeKind; @@ -34,7 +34,7 @@ /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( summary = - "void-returning methods should not be annotated with @Nullable," + "void-returning methods should not be annotated with nullness annotations," + " since they cannot return null", severity = WARNING, tags = StandardTags.STYLE) @@ -46,11 +46,12 @@ public Description matchMethod(MethodTree tree, VisitorState state) { if (sym.getReturnType().getKind() != TypeKind.VOID) { return NO_MATCH; } - AnnotationTree annotation = - ASTHelpers.getAnnotationWithSimpleName(tree.getModifiers().getAnnotations(), "Nullable"); - if (annotation == null) { + var relevantAnnos = annotationsRelevantToNullness(tree.getModifiers().getAnnotations()); + if (relevantAnnos.isEmpty()) { return NO_MATCH; } - return describeMatch(annotation, SuggestedFix.delete(annotation)); + var fix = SuggestedFix.builder(); + relevantAnnos.forEach(fix::delete); + return describeMatch(relevantAnnos.get(0), fix.build()); } } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java index dff29e41b41..c51805a5a8e 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java @@ -36,12 +36,24 @@ public void positive() { "import javax.annotation.Nullable;", "class Test {", " // BUG: Diagnostic contains:", - " // void-returning methods should not be annotated with @Nullable", " @Nullable void f() {}", "}") .doTest(); } + @Test + public void positiveCheckForNull() { + compilationHelper + .addSourceLines( + "Test.java", + "import javax.annotation.CheckForNull;", + "class Test {", + " // BUG: Diagnostic contains:", + " @CheckForNull void f() {}", + "}") + .doTest(); + } + @Test public void negativeNotAnnotated() { compilationHelper From 62c454ef5f4558490f5c73021e813d7b5951eb13 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Mon, 22 Jul 2024 11:58:10 -0700 Subject: [PATCH 06/39] Fix a crash in YodaCondition https://github.com/google/error-prone/issues/4488 PiperOrigin-RevId: 654844314 --- .../errorprone/bugpatterns/YodaCondition.java | 12 ++++++------ .../bugpatterns/YodaConditionTest.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/YodaCondition.java b/core/src/main/java/com/google/errorprone/bugpatterns/YodaCondition.java index aad89b25e9e..f246fb6f706 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/YodaCondition.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/YodaCondition.java @@ -79,12 +79,12 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state); } if (instanceEqualsInvocation().matches(tree, state)) { - return fix( - tree, - getReceiver(tree), - tree.getArguments().get(0), - /* provideNullSafeFix= */ true, - state); + ExpressionTree receiver = getReceiver(tree); + if (receiver == null) { + // call to equals implicitly qualified by `this` + return NO_MATCH; + } + return fix(tree, receiver, tree.getArguments().get(0), /* provideNullSafeFix= */ true, state); } return NO_MATCH; } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/YodaConditionTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/YodaConditionTest.java index 2cfb93d1f32..a2b545b8a98 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/YodaConditionTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/YodaConditionTest.java @@ -251,4 +251,22 @@ public void nullableYodaCondition() { "}") .doTest(); } + + @Test + public void unqualified() { + testHelper + .addSourceLines( + "Test.java", + "import com.google.common.base.Objects;", + "class Test {", + " @Override", + " public boolean equals(Object other) {", + " return Objects.equal(this, other);", + " }", + " public boolean foo(Object other) {", + " return equals(other);", + " }", + "}") + .doTest(); + } } From 868626bb2e543632184f17d3d4971772ea716a5b Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Mon, 22 Jul 2024 12:41:25 -0700 Subject: [PATCH 07/39] Ban more class loading primitives No new violations are found after TGP. PiperOrigin-RevId: 654858529 --- .../java/com/google/errorprone/bugpatterns/BanClassLoader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/BanClassLoader.java b/core/src/main/java/com/google/errorprone/bugpatterns/BanClassLoader.java index f7528a7dd9f..de59bde14b6 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/BanClassLoader.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/BanClassLoader.java @@ -48,6 +48,8 @@ public final class BanClassLoader extends AbstractBanUnsafeAPIChecker private static final Matcher METHOD_MATCHER = anyOf( anyMethod().onDescendantOf("java.lang.ClassLoader").named("defineClass"), + anyMethod().onDescendantOf("jdk.internal.misc.Unsafe").named("defineClass"), + anyMethod().onDescendantOf("jdk.internal.access.JavaLangAccess").named("defineClass"), anyMethod().onDescendantOf("java.lang.invoke.MethodHandles.Lookup").named("defineClass"), anyMethod() .onDescendantOf("java.rmi.server.RMIClassLoader") From fefeef9a9a89053c851f1030865d62a37ae227fb Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 23 Jul 2024 10:15:44 -0700 Subject: [PATCH 08/39] Remove unused dependency on guice See https://github.com/google/error-prone/commit/1c3c09f5c286989f76eb545613c9ce91dac4acad PiperOrigin-RevId: 655210727 --- check_api/pom.xml | 6 +++--- core/pom.xml | 2 +- pom.xml | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/check_api/pom.xml b/check_api/pom.xml index 1a96ffd6473..2c7354ba79c 100644 --- a/check_api/pom.xml +++ b/check_api/pom.xml @@ -138,9 +138,9 @@ - com.google.inject - guice - ${guice.version} + javax.inject + javax.inject + 1 diff --git a/core/pom.xml b/core/pom.xml index db42176e114..55428377101 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -179,7 +179,7 @@ com.google.inject guice - ${guice.version} + 5.1.0 test diff --git a/pom.xml b/pom.xml index 8b5587c1b00..1a2615bb86d 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,6 @@ 3.19.6 1.43.3 1.0.0 - 5.1.0 From f97736d1859267ba9ef9e299c98a1ed0dda74e41 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Tue, 23 Jul 2024 11:06:44 -0700 Subject: [PATCH 09/39] Add kotlin `Result` to WellKnownThreadSafety -[] `Result` is an immutable wrapper over a `T` in the case of success, or `Throwable` in the case of an error. Kotlin docs - https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/ TAP train - [] PiperOrigin-RevId: 655230537 --- .../bugpatterns/threadsafety/WellKnownThreadSafety.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownThreadSafety.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownThreadSafety.java index 9aea947a68a..dc2202906a1 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownThreadSafety.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/WellKnownThreadSafety.java @@ -149,6 +149,7 @@ private static ImmutableMap buildThreadSafeClasses( .add("kotlinx.coroutines.flow.MutableStateFlow", "T") .add("kotlinx.coroutines.sync.Mutex") .add("kotlinx.coroutines.sync.Semaphore") + .add("kotlin.Result", "T") .add("kotlin.Unit") .add("org.bouncycastle.cms.CMSSignedData") .add("org.bouncycastle.pkcs.PKCS10CertificationRequest") From 9f4b7d090895de6fb528b7a2e5032232215e7018 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 23 Jul 2024 11:20:46 -0700 Subject: [PATCH 10/39] Disable implicit annotation processing in some tests Fixes ``` Note: Annotation processing is enabled because one or more processors were found on the class path. A future release of javac may disable annotation processing unless at least one processor is specified by name (-processor), or a search path is specified (--processor-path, --processor-module-path), or annotation processing is enabled explicitly (-proc:only, -proc:full). Use -Xlint:-options to suppress this message. Use -proc:none to disable annotation processing. ``` PiperOrigin-RevId: 655236694 --- .../threadsafety/GuardedByBinderTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinderTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinderTest.java index 5ad07056f18..73dcb750061 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinderTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinderTest.java @@ -21,6 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableList; import com.google.errorprone.FileManagers; import com.google.errorprone.VisitorState; import com.google.errorprone.util.ASTHelpers; @@ -34,7 +35,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Optional; import javax.tools.JavaCompiler; @@ -497,13 +497,13 @@ private static String bind(String className, String exprString, JavaFileObject f JavacTaskImpl task = (JavacTaskImpl) javaCompiler.getTask( - new PrintWriter( + /* out= */ new PrintWriter( new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), - FileManagers.testFileManager(), - null, - Collections.emptyList(), - null, - Arrays.asList(fileObject)); + /* fileManager= */ FileManagers.testFileManager(), + /* diagnosticListener= */ null, + /* options= */ ImmutableList.of("-proc:none"), + /* classes= */ null, + /* compilationUnits= */ Arrays.asList(fileObject)); Iterable compilationUnits = task.parse(); task.analyze(); for (CompilationUnitTree compilationUnit : compilationUnits) { From 996d09cf2ed8e1b753fde897bb7d643fcc7514af Mon Sep 17 00:00:00 2001 From: Thomas Broyer Date: Tue, 23 Jul 2024 11:27:35 -0700 Subject: [PATCH 11/39] Add DefaultLocale check to discourage use of the default locale Fixes #632 Fixes #4487 COPYBARA_INTEGRATE_REVIEW=https://github.com/google/error-prone/pull/4487 from tbroyer:default-locale 102a28f6f473ce1aae996dae4ad54e193473e411 PiperOrigin-RevId: 655239463 --- .../errorprone/bugpatterns/DefaultLocale.java | 699 ++++++++++++++++++ .../scanner/BuiltInCheckerSuppliers.java | 2 + .../bugpatterns/DefaultLocaleTest.java | 493 ++++++++++++ 3 files changed, 1194 insertions(+) create mode 100644 core/src/main/java/com/google/errorprone/bugpatterns/DefaultLocale.java create mode 100644 core/src/test/java/com/google/errorprone/bugpatterns/DefaultLocaleTest.java diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/DefaultLocale.java b/core/src/main/java/com/google/errorprone/bugpatterns/DefaultLocale.java new file mode 100644 index 00000000000..81721028c7e --- /dev/null +++ b/core/src/main/java/com/google/errorprone/bugpatterns/DefaultLocale.java @@ -0,0 +1,699 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Description.NO_MATCH; +import static com.google.errorprone.matchers.FieldMatchers.staticField; +import static com.google.errorprone.matchers.Matchers.anyOf; +import static com.google.errorprone.matchers.Matchers.constructor; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.receiverOfInvocation; +import static com.google.errorprone.matchers.Matchers.staticMethod; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher; +import com.google.errorprone.fixes.Fix; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.suppliers.Supplier; +import com.google.errorprone.suppliers.Suppliers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Type; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Formattable; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ +@BugPattern( + summary = + "Implicit use of the JVM default locale, which can result in differing behaviour between" + + " JVM executions.", + severity = WARNING) +public class DefaultLocale extends BugChecker + implements MethodInvocationTreeMatcher, NewClassTreeMatcher { + + private enum LocaleFix { + ROOT_FIX("Specify ROOT locale") { + @Override + String replacement(SuggestedFix.Builder fix, VisitorState state) { + fix.addImport("java.util.Locale"); + return "Locale.ROOT"; + } + }, + DEFAULT_LOCALE_FIX("Specify default locale") { + @Override + String replacement(SuggestedFix.Builder fix, VisitorState state) { + fix.addImport("java.util.Locale"); + return "Locale.getDefault()"; + } + }, + DEFAULT_DISPLAY_LOCALE_FIX("Specify default display locale") { + @Override + String replacement(SuggestedFix.Builder fix, VisitorState state) { + fix.addImport("java.util.Locale"); + return String.format( + "Locale.getDefault(%s)", + SuggestedFixes.qualifyStaticImport("java.util.Locale.Category.DISPLAY", fix, state)); + } + }, + DEFAULT_FORMAT_LOCALE_FIX("Specify default format locale") { + @Override + String replacement(SuggestedFix.Builder fix, VisitorState state) { + fix.addImport("java.util.Locale"); + return String.format( + "Locale.getDefault(%s)", + SuggestedFixes.qualifyStaticImport("java.util.Locale.Category.FORMAT", fix, state)); + } + }; + + private final String title; + + LocaleFix(String title) { + this.title = title; + } + + String title() { + return title; + } + + abstract String replacement(SuggestedFix.Builder fix, VisitorState state); + } + + private static final Pattern SPECIFIER_ALLOW_LIST_REGEX = + Pattern.compile("%([%n]|(\\d+\\$|<)?-?\\d*(\\.\\d+)?[bhsc])"); + + private static final Supplier FORMATTABLE = Suppliers.typeFromClass(Formattable.class); + + private static final Supplier APPENDABLE = Suppliers.typeFromClass(Appendable.class); + + private static final Supplier PRINTSTREAM = Suppliers.typeFromClass(PrintStream.class); + + private static final ImmutableList> PATTERN_AND_ARGS = + ImmutableList.of(Suppliers.STRING_TYPE, Suppliers.arrayOf(Suppliers.OBJECT_TYPE)); + + private static final Matcher FORMAT_METHODS = + anyOf( + instanceMethod() + .onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName()) + .namedAnyOf("format", "printf") + .withParametersOfType(PATTERN_AND_ARGS), + staticMethod() + .onClass(Suppliers.STRING_TYPE) + .named("format") + .withParametersOfType(PATTERN_AND_ARGS)); + + private static final Matcher SYSTEM_OUT_RECEIVER = + receiverOfInvocation( + anyOf( + staticField(System.class.getName(), "out"), + staticField(System.class.getName(), "err"))); + + private static final Matcher STRING_FORMATTED = + instanceMethod().onExactClass(Suppliers.STRING_TYPE).named("formatted"); + + private static final Matcher DISPLAY_METHODS = + instanceMethod().onExactClass("java.util.Currency").named("getSymbol").withNoParameters(); + + private static final Matcher FACTORIES = + anyOf( + staticMethod() + .onClass("java.text.BreakIterator") + .namedAnyOf( + "getCharacterInstance", + "getLineInstance", + "getSentenceInstance", + "getWordInstance") + .withNoParameters(), + staticMethod().onClass("java.text.Collator").named("getInstance").withNoParameters()); + + private static final Matcher FORMATTER_FACTORIES = + anyOf( + staticMethod() + .onClass("java.text.NumberFormat") + .namedAnyOf( + "getCompactNumberInstance", + "getCurrencyInstance", + "getInstance", + "getIntegerInstance", + "getNumberInstance", + "getPercentInstance") + .withNoParameters(), + staticMethod() + .onClass("java.text.DateFormatSymbols") + .named("getInstance") + .withNoParameters(), + staticMethod() + .onClass("java.text.DecimalFormatSymbols") + .named("getInstance") + .withNoParameters(), + staticMethod() + .onClass("java.time.format.DateTimeFormatter") + .named("ofPattern") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE)), + instanceMethod() + .onDescendantOf("java.time.format.DateTimeFormatterBuilder") + .named("toFormatter") + .withNoParameters()); + + private static final Matcher DATE_FORMAT = + anyOf( + staticMethod().onClass("java.text.DateFormat").named("getInstance").withNoParameters(), + staticMethod() + .onClass("java.text.DateFormat") + .namedAnyOf("getDateInstance", "getTimeInstance") + .withNoParameters(), + staticMethod() + .onClass("java.text.DateFormat") + .namedAnyOf("getDateInstance", "getTimeInstance") + .withParametersOfType(ImmutableList.of(Suppliers.INT_TYPE)), + staticMethod() + .onClass("java.text.DateFormat") + .named("getDateTimeInstance") + .withNoParameters(), + staticMethod() + .onClass("java.text.DateFormat") + .named("getDateTimeInstance") + .withParametersOfType(ImmutableList.of(Suppliers.INT_TYPE, Suppliers.INT_TYPE))); + + private static final Matcher MESSAGEFORMAT_FORMAT = + staticMethod() + .onClass("java.text.MessageFormat") + .named("format") + .withParametersOfType(PATTERN_AND_ARGS); + + private static final Matcher RESOURCE_BUNDLE = + anyOf( + staticMethod() + .onClass("java.util.ResourceBundle") + .named("getBundle") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE)), + staticMethod() + .onClass("java.util.ResourceBundle") + .named("getBundle") + .withParameters("java.lang.String", "java.util.ResourceBundle.Control"), + staticMethod() + .onClass("java.util.ResourceBundle") + .named("getBundle") + .withParameters("java.lang.String", "java.lang.Module")); + + private static final Matcher FORMAT_CONSTRUCTORS = + anyOf( + constructor() + .forClass("java.text.MessageFormat") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE)), + constructor().forClass("java.text.DateFormatSymbols").withNoParameters(), + constructor().forClass("java.text.DecimalFormatSymbols").withNoParameters()); + + private static final Matcher DECIMAL_FORMAT = + anyOf( + constructor().forClass("java.text.DecimalFormat").withNoParameters(), + constructor() + .forClass("java.text.DecimalFormat") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE))); + + private static final Matcher SIMPLE_DATE_FORMAT = + anyOf( + constructor().forClass("java.text.SimpleDateFormat").withNoParameters(), + constructor() + .forClass("java.text.SimpleDateFormat") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE))); + + private static final Matcher FORMATTER = + anyOf( + constructor().forClass("java.util.Formatter").withNoParameters(), + constructor() + .forClass("java.util.Formatter") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE)), + constructor() + .forClass("java.util.Formatter") + .withParametersOfType(ImmutableList.of(Suppliers.STRING_TYPE, Suppliers.STRING_TYPE)), + constructor().forClass("java.util.Formatter").withParameters("java.lang.Appendable"), + constructor().forClass("java.util.Formatter").withParameters("java.io.File"), + constructor() + .forClass("java.util.Formatter") + .withParameters("java.io.File", "java.lang.String"), + constructor().forClass("java.util.Formatter").withParameters("java.io.PrintStream"), + constructor().forClass("java.util.Formatter").withParameters("java.io.OutputStream"), + constructor() + .forClass("java.util.Formatter") + .withParameters("java.io.OutputStream", "java.lang.String")); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + // String.toUpperCase/toLowerCase are already handled by StringCaseLocaleUsage + if (FORMAT_METHODS.matches(tree, state)) { + // Allow System.out and System.err + if (SYSTEM_OUT_RECEIVER.matches(tree, state) + || !shouldRefactorStringFormat( + tree.getArguments().get(0), + tree.getArguments().stream().skip(1).collect(toImmutableList()), + state)) { + return NO_MATCH; + } + return prependLocales( + tree, + state, + LocaleFix.ROOT_FIX, + LocaleFix.DEFAULT_LOCALE_FIX, + LocaleFix.DEFAULT_FORMAT_LOCALE_FIX); + } + if (STRING_FORMATTED.matches(tree, state)) { + return handleStringFormatted(tree, state); + } + if (DISPLAY_METHODS.matches(tree, state)) { + return appendLocales( + tree, + state, + LocaleFix.ROOT_FIX, + LocaleFix.DEFAULT_LOCALE_FIX, + LocaleFix.DEFAULT_DISPLAY_LOCALE_FIX); + } + if (FACTORIES.matches(tree, state)) { + return appendLocales(tree, state, LocaleFix.ROOT_FIX, LocaleFix.DEFAULT_LOCALE_FIX); + } + if (FORMATTER_FACTORIES.matches(tree, state)) { + return appendLocales( + tree, + state, + LocaleFix.ROOT_FIX, + LocaleFix.DEFAULT_LOCALE_FIX, + LocaleFix.DEFAULT_FORMAT_LOCALE_FIX); + } + if (DATE_FORMAT.matches(tree, state)) { + return handleDateFormat(tree, state); + } + if (MESSAGEFORMAT_FORMAT.matches(tree, state)) { + return handleMessageFormatFormat(tree, state); + } + if (RESOURCE_BUNDLE.matches(tree, state)) { + return handleResourceBundle(tree, state); + } + return NO_MATCH; + } + + @Override + public Description matchNewClass(NewClassTree tree, VisitorState state) { + if (FORMAT_CONSTRUCTORS.matches(tree, state)) { + return appendLocales( + tree, + state, + LocaleFix.ROOT_FIX, + LocaleFix.DEFAULT_LOCALE_FIX, + LocaleFix.DEFAULT_FORMAT_LOCALE_FIX); + } + if (DECIMAL_FORMAT.matches(tree, state)) { + return handleDecimalFormat(tree, state); + } + if (SIMPLE_DATE_FORMAT.matches(tree, state)) { + return handleSimpleDateFormat(tree, state); + } + if (FORMATTER.matches(tree, state)) { + return handleFormatter(tree, state); + } + return NO_MATCH; + } + + private Description handleStringFormatted(MethodInvocationTree tree, VisitorState state) { + if (!shouldRefactorStringFormat(ASTHelpers.getReceiver(tree), tree.getArguments(), state)) { + return NO_MATCH; + } + var description = buildDescription(tree); + description.addFix(stringFormattedFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(stringFormattedFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix(stringFormattedFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + return description.build(); + } + + private Fix stringFormattedFix( + MethodInvocationTree tree, VisitorState state, LocaleFix localeFix) { + var fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + fix.replace( + tree, + String.format( + "String.format(%s, %s, %s)", + localeFix.replacement(fix, state), + state.getSourceForNode(ASTHelpers.getReceiver(tree)), + tree.getArguments().stream() + .map(state::getSourceForNode) + .collect(Collectors.joining(", ")))); + return fix.build(); + } + + private Description handleDateFormat(MethodInvocationTree tree, VisitorState state) { + var description = buildDescription(tree); + var methodName = ASTHelpers.getSymbol(tree).getSimpleName(); + if (methodName.contentEquals("getInstance")) { + dateFormatGetInstanceFixes(description, tree, state); + } else if (methodName.contentEquals("getDateTimeInstance")) { + dateFormatFixes(description, tree, state, 2); + } else { + dateFormatFixes(description, tree, state, 1); + } + return description.build(); + } + + private void dateFormatGetInstanceFixes( + Description.Builder description, MethodInvocationTree tree, VisitorState state) { + description.addFix(dateFormatGetInstanceFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(dateFormatGetInstanceFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix(dateFormatGetInstanceFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + } + + private Fix dateFormatGetInstanceFix( + MethodInvocationTree tree, VisitorState state, LocaleFix localeFix) { + var fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + fix.replace( + state.getEndPosition(tree.getMethodSelect()), + state.getEndPosition(tree), + String.format( + "(%1$s, % defaultConst) + .limit(nonLocaleArgs) + .collect(Collectors.joining(", ")), + localeFix.replacement(fix, state))); + } else { + fix.postfixWith( + Iterables.getLast(tree.getArguments()), ", " + localeFix.replacement(fix, state)); + } + return fix.build(); + } + + private Description handleMessageFormatFormat(MethodInvocationTree tree, VisitorState state) { + var pattern = tree.getArguments().get(0); + var arguments = tree.getArguments().stream().skip(1).collect(toImmutableList()); + if (!shouldRefactorStringFormat(pattern, arguments, state)) { + return NO_MATCH; + } + var description = buildDescription(tree); + description.addFix(messageFormatFormatFix(tree, pattern, arguments, state, LocaleFix.ROOT_FIX)); + description.addFix( + messageFormatFormatFix(tree, pattern, arguments, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix( + messageFormatFormatFix( + tree, pattern, arguments, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + return description.build(); + } + + /** + * Should only refactor String.format() and similar methods' invocations where specifiers aren't + * locale-dependent. For %s this includes checking for non-Formattable arguments. Format strings + * (first argument) as variables or constants are excluded from refactoring. + */ + private boolean shouldRefactorStringFormat( + ExpressionTree pattern, List arguments, VisitorState state) { + String patternValue = ASTHelpers.constValue(pattern, String.class); + // TODO: add a flag to be stricter and reformat whenever the pattern is not a constant + if (patternValue != null && !onlyContainsSpecifiersInAllowList(patternValue)) { + return true; + } + // Ideally we'd only check for Formattable on arguments used in %s specifiers + return containsSomeFormattableArgument(arguments, state); + } + + @VisibleForTesting + static boolean onlyContainsSpecifiersInAllowList(String pattern) { + var noSpecifierFormatBase = SPECIFIER_ALLOW_LIST_REGEX.matcher(pattern).replaceAll(""); + // If it still has a specifier after the replacement, it means that it was not on the allowlist. + return !noSpecifierFormatBase.contains("%"); + } + + private boolean containsSomeFormattableArgument( + List arguments, VisitorState state) { + return arguments.stream().anyMatch(tree -> mightBeFormattable(tree, state)); + } + + private boolean mightBeFormattable(ExpressionTree tree, VisitorState state) { + if (tree instanceof LiteralTree) { + return false; + } + // TODO: add a flag to be stricter and detect any argument that could be cast to Formattable + // (rather than only the ones that are proven to be Formattable) + return ASTHelpers.isSubtype(ASTHelpers.getResultType(tree), FORMATTABLE.get(state), state); + } + + private Fix messageFormatFormatFix( + MethodInvocationTree tree, + ExpressionTree pattern, + ImmutableList arguments, + VisitorState state, + LocaleFix localeFix) { + var fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + fix.replace( + tree, + String.format( + "new %s(%s, %s).format(%s)", + SuggestedFixes.qualifyType(state, fix, "java.text.MessageFormat"), + state.getSourceForNode(pattern), + localeFix.replacement(fix, state), + arguments.stream().map(state::getSourceForNode).collect(Collectors.joining(", ")))); + return fix.build(); + } + + private Description handleResourceBundle(MethodInvocationTree tree, VisitorState state) { + var description = buildDescription(tree); + description.addFix(resourceBundleFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(resourceBundleFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + return description.build(); + } + + private Fix resourceBundleFix( + MethodInvocationTree tree, VisitorState state, LocaleFix localeFix) { + var fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + fix.postfixWith(tree.getArguments().get(0), ", " + localeFix.replacement(fix, state)); + return fix.build(); + } + + private Description handleDecimalFormat(NewClassTree tree, VisitorState state) { + var description = buildDescription(tree); + if (tree.getArguments().isEmpty()) { + description.addFix(decimalFormatToNumberFormatFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(decimalFormatToNumberFormatFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix( + decimalFormatToNumberFormatFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + } else { + description.addFix(decimalFormatFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(decimalFormatFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix(decimalFormatFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + } + return description.build(); + } + + private Fix decimalFormatToNumberFormatFix( + NewClassTree tree, VisitorState state, LocaleFix localeFix) { + var fix = + SuggestedFix.builder() + .setShortDescription(localeFix.title()) + .addImport("java.text.NumberFormat"); + fix.replace( + tree, String.format("NumberFormat.getInstance(%s)", localeFix.replacement(fix, state))); + return fix.build(); + } + + private Fix decimalFormatFix(NewClassTree tree, VisitorState state, LocaleFix localeFix) { + var fix = + SuggestedFix.builder() + .setShortDescription(localeFix.title()) + .addImport("java.text.DecimalFormatSymbols"); + fix.postfixWith( + Iterables.getLast(tree.getArguments()), + String.format(", DecimalFormatSymbols.getInstance(%s)", localeFix.replacement(fix, state))); + return fix.build(); + } + + private Description handleSimpleDateFormat(NewClassTree tree, VisitorState state) { + var description = buildDescription(tree); + if (tree.getArguments().isEmpty()) { + description.addFix(simpleDateFormatToDateFormatFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix( + simpleDateFormatToDateFormatFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix( + simpleDateFormatToDateFormatFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + } else { + description.addFix(simpleDateFormatFix(tree, state, LocaleFix.ROOT_FIX)); + description.addFix(simpleDateFormatFix(tree, state, LocaleFix.DEFAULT_LOCALE_FIX)); + description.addFix(simpleDateFormatFix(tree, state, LocaleFix.DEFAULT_FORMAT_LOCALE_FIX)); + } + return description.build(); + } + + private Fix simpleDateFormatToDateFormatFix( + NewClassTree tree, VisitorState state, LocaleFix localeFix) { + var fix = + SuggestedFix.builder() + .setShortDescription(localeFix.title()) + .addImport("java.text.DateFormat"); + fix.replace( + tree, + String.format( + "DateFormat.getDateTimeInstance(%1$s, % arguments, + VisitorState state, + LocaleFix... localeFixes) { + Description.Builder description = buildDescription(tree); + for (LocaleFix localeFix : localeFixes) { + description.addFix(prependLocale(tree, select, arguments, state, localeFix)); + } + return description.build(); + } + + private Fix prependLocale( + Tree tree, + Tree select, + List arguments, + VisitorState state, + LocaleFix localeFix) { + SuggestedFix.Builder fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + if (arguments.isEmpty()) { + fix.replace( + state.getEndPosition(select), + state.getEndPosition(tree), + String.format("(%s)", localeFix.replacement(fix, state))); + } else { + fix.prefixWith(arguments.get(0), localeFix.replacement(fix, state) + ", "); + } + return fix.build(); + } + + private Description appendLocales( + MethodInvocationTree tree, VisitorState state, LocaleFix... localeFixes) { + return appendLocales(tree, tree.getMethodSelect(), tree.getArguments(), state, localeFixes); + } + + private Description appendLocales( + NewClassTree tree, VisitorState state, LocaleFix... localeFixes) { + return appendLocales(tree, tree.getIdentifier(), tree.getArguments(), state, localeFixes); + } + + private Description appendLocales( + Tree tree, + Tree select, + List arguments, + VisitorState state, + LocaleFix... localeFixes) { + Description.Builder description = buildDescription(tree); + for (LocaleFix localeFix : localeFixes) { + description.addFix(appendLocale(tree, select, arguments, state, localeFix)); + } + return description.build(); + } + + private Fix appendLocale( + Tree tree, + Tree select, + List arguments, + VisitorState state, + LocaleFix localeFix) { + SuggestedFix.Builder fix = SuggestedFix.builder().setShortDescription(localeFix.title()); + if (arguments.isEmpty()) { + fix.replace( + state.getEndPosition(select), + state.getEndPosition(tree), + String.format("(%s)", localeFix.replacement(fix, state))); + } else { + fix.postfixWith(Iterables.getLast(arguments), ", " + localeFix.replacement(fix, state)); + } + return fix.build(); + } +} diff --git a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java index ccd20eae719..e020cace23f 100644 --- a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java +++ b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java @@ -105,6 +105,7 @@ import com.google.errorprone.bugpatterns.DeduplicateConstants; import com.google.errorprone.bugpatterns.DeeplyNested; import com.google.errorprone.bugpatterns.DefaultCharset; +import com.google.errorprone.bugpatterns.DefaultLocale; import com.google.errorprone.bugpatterns.DefaultPackage; import com.google.errorprone.bugpatterns.DepAnn; import com.google.errorprone.bugpatterns.DeprecatedVariable; @@ -1143,6 +1144,7 @@ public static ScannerSupplier warningChecks() { ConstantField.class, ConstantPatternCompile.class, DeduplicateConstants.class, + DefaultLocale.class, // TODO: enable this by default. DepAnn.class, DifferentNameButSame.class, DoNotUseRuleChain.class, diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/DefaultLocaleTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/DefaultLocaleTest.java new file mode 100644 index 00000000000..1bac680baac --- /dev/null +++ b/core/src/test/java/com/google/errorprone/bugpatterns/DefaultLocaleTest.java @@ -0,0 +1,493 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static com.google.common.base.Predicates.containsPattern; +import static com.google.errorprone.bugpatterns.DefaultLocale.onlyContainsSpecifiersInAllowList; +import static java.util.function.Predicate.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers; +import com.google.errorprone.CompilationTestHelper; +import com.google.errorprone.util.RuntimeVersion; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** {@link DefaultLocale}Test */ +@RunWith(JUnit4.class) +public class DefaultLocaleTest { + + private final CompilationTestHelper compilationHelper = + CompilationTestHelper.newInstance(DefaultLocale.class, getClass()); + + private BugCheckerRefactoringTestHelper refactoringTest() { + return BugCheckerRefactoringTestHelper.newInstance(DefaultLocale.class, getClass()); + } + + @Test + public void testOnlyContainsSpecifiersInAllowList() { + assertTrue(onlyContainsSpecifiersInAllowList("%%%n%b%h%c%s")); + assertTrue(onlyContainsSpecifiersInAllowList("%1$s%= 9); + compilationHelper + .addSourceLines( + "Test.java", + "import java.util.*;", + "class Test {", + " void f(ResourceBundle.Control control, Locale locale, Module module) throws" + + " Exception {", + " // BUG: Diagnostic contains: ResourceBundle.getBundle(\"name\"," + + " Locale.getDefault(), module);", + " ResourceBundle.getBundle(\"name\", module);", + // negative + " ResourceBundle.getBundle(\"name\", locale, module);", + " }", + "}") + .doTest(); + } + + @Test + public void formatConstructors() { + compilationHelper + .addSourceLines( + "Test.java", + "import java.io.*;", + "import java.text.*;", + "class Test {", + " void f() throws Exception {", + " // BUG: Diagnostic contains: new MessageFormat(\"%d\"," + + " Locale.getDefault(FORMAT));", + " new MessageFormat(\"%d\");", + " // BUG: Diagnostic contains: new DateFormatSymbols(Locale.getDefault(FORMAT));", + " new DateFormatSymbols();", + " // BUG: Diagnostic contains: new DecimalFormatSymbols(Locale.getDefault(FORMAT));", + " new DecimalFormatSymbols();", + " }", + "}") + .doTest(); + } + + @Test + public void decimalFormat() { + compilationHelper + .addSourceLines( + "Test.java", + "import java.text.*;", + "class Test {", + " void f(DecimalFormatSymbols dfs) throws Exception {", + " // BUG: Diagnostic contains: NumberFormat.getInstance(Locale.getDefault(FORMAT));", + " new DecimalFormat();", + " // BUG: Diagnostic contains: new DecimalFormat(\"000\"," + + " DecimalFormatSymbols.getInstance(Locale.getDefault(FORMAT)));", + " new DecimalFormat(\"000\");", + // negative + " new DecimalFormat(\"000\", dfs);", + " }", + "}") + .doTest(); + } + + @Test + public void simpleDateFormat() { + compilationHelper + .addSourceLines( + "Test.java", + "import java.text.*;", + "import java.util.*;", + "class Test {", + " void f(Locale locale, DateFormatSymbols dfs) throws Exception {", + " // BUG: Diagnostic contains: DateFormat.getDateTimeInstance(SHORT, SHORT," + + " Locale.getDefault(FORMAT));", + " new SimpleDateFormat();", + " // BUG: Diagnostic contains: new SimpleDateFormat(\"yMd\"," + + " Locale.getDefault(FORMAT));", + " new SimpleDateFormat(\"yMd\");", + // negative + " new SimpleDateFormat(\"yMd\", locale);", + " new SimpleDateFormat(\"yMd\", dfs);", + " }", + "}") + .doTest(); + } + + @Test + public void formatter() { + compilationHelper + .addSourceLines( + "Test.java", + "import static java.nio.charset.StandardCharsets.*;", + "import java.io.*;", + "import java.text.*;", + "import java.util.*;", + "class Test {", + " void f(Locale locale, File file, OutputStream os) throws Exception {", + " // BUG: Diagnostic contains: new Formatter(Locale.getDefault(FORMAT));", + " new Formatter();", + " // BUG: Diagnostic contains: new Formatter(new StringBuilder()," + + " Locale.getDefault(FORMAT));", + " new Formatter(new StringBuilder());", + " // BUG: Diagnostic matches: NoFix", + " new Formatter(\"filename\");", + " // BUG: Diagnostic contains: new Formatter(\"filename\", \"utf8\"," + + " Locale.getDefault(FORMAT));", + " new Formatter(\"filename\", \"utf8\");", + " // BUG: Diagnostic matches: NoFix", + " new Formatter(file);", + " // BUG: Diagnostic contains: new Formatter(file, \"utf8\"," + + " Locale.getDefault(FORMAT));", + " new Formatter(file, \"utf8\");", + " // BUG: Diagnostic matches: NoFix", + " new Formatter(System.out);", + " // BUG: Diagnostic matches: NoFix", + " new Formatter(os);", + " // BUG: Diagnostic contains: new Formatter(os, \"utf8\"," + + " Locale.getDefault(FORMAT));", + " new Formatter(os, \"utf8\");", + // negative + " new Formatter(locale);", + " new Formatter(new StringBuilder(), locale);", + " new Formatter(\"filename\", \"utf8\", locale);", + " new Formatter(\"filename\", UTF_8, locale);", + " new Formatter(file, \"utf8\", locale);", + " new Formatter(file, UTF_8, locale);", + " new Formatter(os, \"utf8\", locale);", + " new Formatter(os, UTF_8, locale);", + " }", + "}") + .expectErrorMessage("NoFix", not(containsPattern("Did you mean"))) + .doTest(); + } + + @Test + public void refactoringAddLocaleImport() { + refactoringTest() + .addInputLines( + "Test.java", + "import java.text.*;", + "class Test {", + " void f() throws Exception {", + " MessageFormat.format(\"%d\", 42);", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.text.*;", + "import java.util.Locale;", + "class Test {", + " void f() throws Exception {", + " new MessageFormat(\"%d\", Locale.ROOT).format(42);", + " }", + "}") + .doTest(); + } + + @Test + public void refactoringAddLocaleCategoryFormatStaticImport() { + refactoringTest() + .addInputLines( + "Test.java", + "import java.text.*;", + "class Test {", + " void f() throws Exception {", + " MessageFormat.format(\"%d\", 42);", + " }", + "}") + .addOutputLines( + "Test.java", + "import static java.util.Locale.Category.FORMAT;", + "import java.text.*;", + "import java.util.Locale;", + "class Test {", + " void f() throws Exception {", + " new MessageFormat(\"%d\", Locale.getDefault(FORMAT)).format(42);", + " }", + "}") + .setFixChooser(FixChoosers.THIRD) + .doTest(); + } +} From 7b58ec974205eb11d8789a11bbc528a8af5788cb Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 23 Jul 2024 13:44:38 -0700 Subject: [PATCH 12/39] Add more tests for arrow and multi-case switch statements in MissingCasesInEnumSwitchTest PiperOrigin-RevId: 655288858 --- .../MissingCasesInEnumSwitchTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitchTest.java index 1dbee697e67..887db8d7118 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/MissingCasesInEnumSwitchTest.java @@ -160,4 +160,63 @@ public void empty() { "}") .doTest(); } + + @Test + public void nonExhaustive_arrowStatement() { + assumeTrue(RuntimeVersion.isAtLeast14()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " enum Case { ONE, TWO }", + " void m(Case c) {", + " // BUG: Diagnostic contains: TWO", + " switch (c) {", + " case ONE -> {", + " System.err.println(\"found it!\");", + " }", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void nonExhaustive_multi() { + assumeTrue(RuntimeVersion.isAtLeast14()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " enum Case { ONE, TWO, THREE }", + " void m(Case c) {", + " // BUG: Diagnostic contains: THREE", + " switch (c) {", + " case ONE, TWO:", + " System.err.println(\"found it!\");", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void nonExhaustive_multiArrow() { + assumeTrue(RuntimeVersion.isAtLeast14()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " enum Case { ONE, TWO, THREE }", + " void m(Case c) {", + " // BUG: Diagnostic contains: THREE", + " switch (c) {", + " case ONE, TWO -> {", + " System.err.println(\"found it!\");", + " }", + " }", + " }", + "}") + .doTest(); + } } From 0ec88c0f8f33fb774836267be2f4bd86185414ee Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 23 Jul 2024 14:15:52 -0700 Subject: [PATCH 13/39] Handle multiple labels in UnnecessaryDefaultInEnumSwitch Fixes https://github.com/google/error-prone/issues/4443 PiperOrigin-RevId: 655300423 --- .../UnnecessaryDefaultInEnumSwitch.java | 2 +- .../UnnecessaryDefaultInEnumSwitchTest.java | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitch.java index 8070fb90bc9..5f792aa73d9 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitch.java @@ -231,7 +231,7 @@ private static boolean trivialDefault(List defaultState private static SetView unhandledCases(SwitchTree tree, TypeSymbol switchType) { ImmutableSet handledCases = tree.getCases().stream() - .map(CaseTree::getExpression) + .flatMap(ASTHelpers::getCaseExpressions) .filter(IdentifierTree.class::isInstance) .map(p -> ((IdentifierTree) p).getName().toString()) .collect(toImmutableSet()); diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitchTest.java index 98cf3c03d78..4ef8b07473d 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryDefaultInEnumSwitchTest.java @@ -821,4 +821,30 @@ public void unrecognizedCaseKindRule() { "}") .doTest(); } + + @Test + public void multipleLabels() { + assumeTrue(RuntimeVersion.isAtLeast14()); + compilationHelper + .addSourceLines( + "Test.java", + "class Test {", + " enum Type {", + " FOO, BAR, BAZ,", + " }", + " public static void main(String[] args) {", + " var type = Type.valueOf(args[0]);", + " switch (type) {", + " case FOO -> {", + " System.out.println(\"Hi foo\");", + " }", + " case BAR, BAZ -> {", + " }", + " // BUG: Diagnostic contains: UnnecessaryDefaultInEnumSwitch", + " default -> throw new AssertionError(type);", + " }", + " }", + "}") + .doTest(); + } } From fc339e0e599b90757e116fb580c0810d572da0c8 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Wed, 24 Jul 2024 09:50:43 -0700 Subject: [PATCH 14/39] Disallow unnecessary `break` statements at the end of the body of a `->` switch statement Startblock: * unknown commit is submitted PiperOrigin-RevId: 655602239 --- .../google/errorprone/util/ASTHelpers.java | 27 ++++ .../bugpatterns/UnnecessaryBreakInSwitch.java | 63 +++++++++ .../scanner/BuiltInCheckerSuppliers.java | 2 + .../UnnecessaryBreakInSwitchTest.java | 123 ++++++++++++++++++ docs/bugpattern/UnnecessaryBreakInSwitch.md | 3 + 5 files changed, 218 insertions(+) create mode 100644 core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java create mode 100644 core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java create mode 100644 docs/bugpattern/UnnecessaryBreakInSwitch.md diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index 79bf0896a8f..84c99e6a853 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -2836,6 +2836,33 @@ private static Method getGetCaseKindMethod() { } } + /** + * Returns the statement or expression after the arrow for a {@link CaseTree} of the form: {@code + * case -> }. + */ + @Nullable + public static Tree getCaseTreeBody(CaseTree caseTree) { + if (GET_CASE_BODY_METHOD == null) { + return null; + } + try { + return (Tree) GET_CASE_BODY_METHOD.invoke(caseTree); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + + @Nullable private static final Method GET_CASE_BODY_METHOD = getGetCaseBodyMethod(); + + @Nullable + private static Method getGetCaseBodyMethod() { + try { + return CaseTree.class.getMethod("getBody"); + } catch (NoSuchMethodException e) { + return null; + } + } + /** * Retrieves a stream containing all case expressions, in order, for a given {@code CaseTree}. * This method acts as a facade to the {@code CaseTree.getExpressions()} API, falling back to diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java new file mode 100644 index 00000000000..8deb44f59fc --- /dev/null +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static com.google.common.collect.Iterables.getLast; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Description.NO_MATCH; +import static com.google.errorprone.util.ASTHelpers.isRuleKind; + +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.BreakTree; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; + +/** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ +@BugPattern( + summary = "This break is unnecessary, fallthrough does not occur in -> switches", + severity = WARNING) +public class UnnecessaryBreakInSwitch extends BugChecker implements BugChecker.CaseTreeMatcher { + @Override + public Description matchCase(CaseTree tree, VisitorState state) { + if (!isRuleKind(tree)) { + return NO_MATCH; + } + Tree body = ASTHelpers.getCaseTreeBody(tree); + if (!(body instanceof BlockTree)) { + return NO_MATCH; + } + BlockTree blockTree = (BlockTree) body; + if (blockTree.getStatements().isEmpty()) { + return NO_MATCH; + } + StatementTree last = getLast(blockTree.getStatements()); + if (!(last instanceof BreakTree)) { + return NO_MATCH; + } + BreakTree breakTree = (BreakTree) last; + if (breakTree.getLabel() != null) { + return NO_MATCH; + } + return describeMatch(last, SuggestedFix.delete(last)); + } +} diff --git a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java index e020cace23f..e02825e352a 100644 --- a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java +++ b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java @@ -410,6 +410,7 @@ import com.google.errorprone.bugpatterns.UnnecessaryAsync; import com.google.errorprone.bugpatterns.UnnecessaryBoxedAssignment; import com.google.errorprone.bugpatterns.UnnecessaryBoxedVariable; +import com.google.errorprone.bugpatterns.UnnecessaryBreakInSwitch; import com.google.errorprone.bugpatterns.UnnecessaryDefaultInEnumSwitch; import com.google.errorprone.bugpatterns.UnnecessaryFinal; import com.google.errorprone.bugpatterns.UnnecessaryLambda; @@ -1091,6 +1092,7 @@ public static ScannerSupplier warningChecks() { UnicodeEscape.class, UnnecessaryAssignment.class, UnnecessaryAsync.class, + UnnecessaryBreakInSwitch.class, UnnecessaryLambda.class, UnnecessaryLongToIntConversion.class, UnnecessaryMethodInvocationMatcher.class, diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java new file mode 100644 index 00000000000..9e3ed40d0af --- /dev/null +++ b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static org.junit.Assume.assumeTrue; + +import com.google.errorprone.CompilationTestHelper; +import com.google.errorprone.util.RuntimeVersion; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UnnecessaryBreakInSwitchTest { + + private final CompilationTestHelper testHelper = + CompilationTestHelper.newInstance(UnnecessaryBreakInSwitch.class, getClass()); + + @Test + public void positive() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " switch (i) {", + " default -> {", + " // BUG: Diagnostic contains: break is unnecessary", + " break;", + " }", + " };", + " }", + "}") + .doTest(); + } + + @Test + public void negativeTraditional() { + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " switch (i) {", + " default:", + " break;", + " };", + " }", + "}") + .doTest(); + } + + @Test + public void negativeEmpty() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " switch (i) {", + " default -> {", + " }", + " };", + " }", + "}") + .doTest(); + } + + @Test + public void negativeNotLast() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " switch (i) {", + " default -> {", + " if (true) break;", + " }", + " };", + " }", + "}") + .doTest(); + } + + @Test + public void negativeLabelledBreak() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " outer:", + " while (true) {", + " switch (i) {", + " default -> {", + " break outer;", + " }", + " }", + " }", + " }", + "}") + .doTest(); + } +} diff --git a/docs/bugpattern/UnnecessaryBreakInSwitch.md b/docs/bugpattern/UnnecessaryBreakInSwitch.md new file mode 100644 index 00000000000..dcf29f6d306 --- /dev/null +++ b/docs/bugpattern/UnnecessaryBreakInSwitch.md @@ -0,0 +1,3 @@ +The newer arrow (`->`) syntax for switches does not permit fallthrough between +cases. A `break` statement is allowed to break out of the switch, but including +a `break` as the last statement in a case body is unnecessary. From a94279cf624b035060fddbe4c2a5bf3e2d9de616 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Wed, 24 Jul 2024 13:51:15 -0700 Subject: [PATCH 15/39] Improve fix to prepare for a change to the return type of `JCCompilationUnit#getImports` in an upcoming JDK version Follow-up to https://github.com/google/error-prone/commit/9e0fbf705dc98faf2a8ac88cbdb1facc0ba18696 This allows the code to work if it's compiled and executed against different javac versions. PiperOrigin-RevId: 655688755 --- .../com/google/errorprone/refaster/ImportPolicy.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/errorprone/refaster/ImportPolicy.java b/core/src/main/java/com/google/errorprone/refaster/ImportPolicy.java index c351d02288a..1576928a56d 100644 --- a/core/src/main/java/com/google/errorprone/refaster/ImportPolicy.java +++ b/core/src/main/java/com/google/errorprone/refaster/ImportPolicy.java @@ -266,7 +266,7 @@ private static ImmutableSet getAllImports(Inliner inliner, WhichImports whichImports.getExistingImports(inliner), Optional.ofNullable(inliner.getContext()) .map(c -> c.get(JCCompilationUnit.class)) - .map(JCCompilationUnit::getImports) + .map(ImportPolicy::getImports) .map(Collection::stream) .orElse(Stream.of()) .filter(JCImport.class::isInstance) @@ -276,6 +276,15 @@ private static ImmutableSet getAllImports(Inliner inliner, WhichImports .collect(toImmutableSet()); } + @SuppressWarnings("unchecked") + private static Collection getImports(JCCompilationUnit unit) { + try { + return (Collection) JCCompilationUnit.class.getMethod("getImports").invoke(unit); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + private static JCTree getQualifiedIdentifier(JCImport i) { try { return (JCTree) JCImport.class.getMethod("getQualifiedIdentifier").invoke(i); From bfe69f8afb06113e103a161132189daf0a67844c Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Wed, 24 Jul 2024 14:05:32 -0700 Subject: [PATCH 16/39] Migrate Error Prone to use JSpecify nullness annotations PiperOrigin-RevId: 655693769 --- check_api/pom.xml | 8 +- .../BaseErrorProneJavaCompiler.java | 2 +- .../com/google/errorprone/BugCheckerInfo.java | 5 +- .../com/google/errorprone/VisitorState.java | 35 ++-- .../google/errorprone/apply/DiffApplier.java | 5 +- .../google/errorprone/apply/DiffSupplier.java | 2 +- .../errorprone/dataflow/AccessPath.java | 11 +- .../errorprone/dataflow/AccessPathStore.java | 5 +- .../dataflow/ConstantPropagationAnalysis.java | 5 +- .../google/errorprone/dataflow/DataFlow.java | 8 +- .../NullnessAnnotations.java | 2 +- .../NullnessPropagationTransfer.java | 16 +- .../TrustingNullnessPropagation.java | 2 +- .../inference/NullnessQualifierInference.java | 5 +- .../google/errorprone/fixes/AppliedFix.java | 5 +- .../google/errorprone/fixes/SuggestedFix.java | 2 +- .../errorprone/fixes/SuggestedFixes.java | 10 +- .../matchers/AnnotationMatcherUtils.java | 5 +- .../errorprone/matchers/Description.java | 10 +- .../errorprone/matchers/FieldMatchers.java | 2 +- .../matchers/method/BaseMethodMatcher.java | 2 +- .../method/MethodInvocationMatcher.java | 4 +- .../google/errorprone/util/ASTHelpers.java | 170 +++++++----------- .../errorprone/util/ErrorProneScope.java | 2 +- .../errorprone/util/FindIdentifiers.java | 8 +- core/pom.xml | 2 +- .../AbstractJUnit4InitMethodNotRun.java | 2 +- .../bugpatterns/AbstractMockChecker.java | 2 +- .../AbstractMustBeClosedChecker.java | 5 +- .../bugpatterns/AbstractUseSwitch.java | 2 +- .../bugpatterns/AlreadyChecked.java | 2 +- .../errorprone/bugpatterns/AlwaysThrows.java | 2 +- .../bugpatterns/AnnotateFormatMethod.java | 5 +- .../bugpatterns/AnnotationPosition.java | 6 +- .../bugpatterns/AssertionFailureIgnored.java | 5 +- .../BadAnnotationImplementation.java | 5 +- .../BoxedPrimitiveConstructor.java | 5 +- .../bugpatterns/CatchingUnchecked.java | 2 +- .../ChainedAssertionLosesContext.java | 14 +- .../bugpatterns/CheckReturnValue.java | 2 +- .../bugpatterns/ComplexBooleanConstant.java | 2 +- .../bugpatterns/ConstantOverflow.java | 35 ++-- .../bugpatterns/ConstantPatternCompile.java | 2 +- .../bugpatterns/DeduplicateConstants.java | 8 +- .../bugpatterns/DuplicateDateFormatField.java | 4 +- .../bugpatterns/EqualsHashCode.java | 5 +- .../errorprone/bugpatterns/EqualsNaN.java | 5 +- .../bugpatterns/ExpectedExceptionChecker.java | 2 +- .../bugpatterns/ForEachIterable.java | 5 +- .../bugpatterns/ForOverrideChecker.java | 8 +- .../ImplementAssertionWithChaining.java | 14 +- .../bugpatterns/ImpossibleNullComparison.java | 42 ++--- .../InstanceOfAndCastMatchWrongType.java | 5 +- .../InterruptedExceptionSwallowed.java | 2 +- .../IsInstanceIncompatibleType.java | 2 +- .../errorprone/bugpatterns/JdkObsolete.java | 11 +- .../bugpatterns/LockNotBeforeTry.java | 2 +- .../LongLiteralLowerCaseSuffix.java | 5 +- .../bugpatterns/MissingTestCall.java | 5 +- .../ModifySourceCollectionInStream.java | 2 +- .../NarrowingCompoundAssignment.java | 5 +- .../bugpatterns/NonCanonicalType.java | 5 +- .../errorprone/bugpatterns/NullOptional.java | 2 +- .../bugpatterns/RedundantOverride.java | 5 +- .../bugpatterns/RedundantSetterCall.java | 2 +- .../bugpatterns/RemoveUnusedImports.java | 2 +- .../bugpatterns/RestrictedApiChecker.java | 8 +- .../bugpatterns/SameNameButDifferent.java | 2 +- .../bugpatterns/SelfAssignment.java | 2 +- .../errorprone/bugpatterns/SelfEquals.java | 5 +- .../errorprone/bugpatterns/StaticImports.java | 8 +- .../bugpatterns/StringSplitter.java | 5 +- .../errorprone/bugpatterns/SwitchDefault.java | 2 +- .../bugpatterns/TestExceptionChecker.java | 5 +- .../bugpatterns/TruthAssertExpected.java | 11 +- .../bugpatterns/TypeCompatibility.java | 11 +- .../UnnecessaryAnonymousClass.java | 6 +- .../bugpatterns/UnnecessaryLambda.java | 2 +- .../bugpatterns/UnusedVariable.java | 2 +- .../errorprone/bugpatterns/UseEnumSwitch.java | 2 +- .../android/FragmentInjection.java | 5 +- .../android/IsLoggableTagLength.java | 8 +- .../bugpatterns/apidiff/ApiDiffChecker.java | 2 +- .../EnclosedByReverseHeuristic.java | 2 +- .../LowInformationNameHeuristic.java | 2 +- ...ractCollectionIncompatibleTypeMatcher.java | 30 ++-- .../BinopMatcher.java | 20 +-- .../CompatibleWithMisuse.java | 2 +- .../ContainmentMatchers.java | 6 +- .../IncompatibleArgumentType.java | 19 +- .../MethodArgMatcher.java | 11 +- .../TypeArgOfMethodArgMatcher.java | 11 +- .../flogger/FloggerArgumentToString.java | 8 +- .../flogger/FloggerFormatString.java | 6 +- .../flogger/FloggerRedundantIsEnabled.java | 2 +- .../flogger/FloggerWithoutCause.java | 5 +- .../formatstring/FormatStringValidation.java | 14 +- .../formatstring/InlineFormatString.java | 2 +- .../StrictFormatStringValidation.java | 12 +- .../inject/MissingRuntimeRetention.java | 2 +- .../dagger/AndroidInjectionBeforeSuper.java | 2 +- .../inlineme/InlinabilityResult.java | 2 +- .../bugpatterns/javadoc/EscapedEntity.java | 2 +- .../bugpatterns/javadoc/MissingSummary.java | 5 +- .../bugpatterns/javadoc/UnescapedEntity.java | 2 +- .../javadoc/UnrecognisedJavadocTag.java | 2 +- .../errorprone/bugpatterns/javadoc/Utils.java | 8 +- .../nullness/EqualsBrokenForNull.java | 5 +- .../bugpatterns/nullness/NullnessUtils.java | 17 +- .../threadsafety/ConstantExpressions.java | 2 +- .../threadsafety/DoubleCheckedLocking.java | 11 +- .../threadsafety/GuardedByBinder.java | 2 +- .../threadsafety/GuardedByChecker.java | 2 +- .../threadsafety/GuardedBySymbolResolver.java | 26 +-- .../threadsafety/GuardedByUtils.java | 2 +- .../threadsafety/ImmutableAnalysis.java | 2 +- .../threadsafety/ImmutableChecker.java | 2 +- .../threadsafety/ImmutableRefactoring.java | 19 +- .../threadsafety/ThreadSafeChecker.java | 2 +- .../threadsafety/ThreadSafety.java | 8 +- .../bugpatterns/time/TimeUnitMismatch.java | 8 +- .../google/errorprone/refaster/Bindings.java | 5 +- .../refaster/ExpressionTemplate.java | 2 +- .../PlaceholderUnificationVisitor.java | 5 +- .../errorprone/refaster/StringName.java | 2 +- .../google/errorprone/refaster/Template.java | 6 +- .../errorprone/refaster/UAnnotation.java | 5 +- .../errorprone/refaster/UArrayType.java | 5 +- .../errorprone/refaster/UArrayTypeTree.java | 5 +- .../google/errorprone/refaster/UAssert.java | 5 +- .../google/errorprone/refaster/UBreak.java | 8 +- .../google/errorprone/refaster/UCatch.java | 5 +- .../errorprone/refaster/UClassIdent.java | 8 +- .../errorprone/refaster/UConditional.java | 5 +- .../google/errorprone/refaster/UContinue.java | 5 +- .../errorprone/refaster/UDoWhileLoop.java | 6 +- .../refaster/UExpressionStatement.java | 5 +- .../google/errorprone/refaster/UForLoop.java | 8 +- .../errorprone/refaster/UFreeIdent.java | 2 +- .../com/google/errorprone/refaster/UIf.java | 9 +- .../errorprone/refaster/UInstanceOf.java | 6 +- .../refaster/ULabeledStatement.java | 5 +- .../google/errorprone/refaster/ULiteral.java | 5 +- .../google/errorprone/refaster/UMatches.java | 5 +- .../errorprone/refaster/UMemberReference.java | 5 +- .../errorprone/refaster/UMethodDecl.java | 5 +- .../refaster/UMethodInvocation.java | 5 +- .../errorprone/refaster/UMethodType.java | 5 +- .../google/errorprone/refaster/UNewArray.java | 14 +- .../google/errorprone/refaster/UNewClass.java | 11 +- .../google/errorprone/refaster/UOfKind.java | 5 +- .../google/errorprone/refaster/URepeated.java | 8 +- .../google/errorprone/refaster/UReturn.java | 8 +- .../errorprone/refaster/UTemplater.java | 17 +- .../google/errorprone/refaster/UThrow.java | 5 +- .../com/google/errorprone/refaster/UTree.java | 2 +- .../com/google/errorprone/refaster/UTry.java | 12 +- .../com/google/errorprone/refaster/UType.java | 5 +- .../errorprone/refaster/UTypeApply.java | 5 +- .../errorprone/refaster/UTypeParameter.java | 6 +- .../google/errorprone/refaster/UTypeVar.java | 5 +- .../errorprone/refaster/UTypeVarIdent.java | 2 +- .../google/errorprone/refaster/UUnary.java | 5 +- .../errorprone/refaster/UVariableDecl.java | 5 +- .../google/errorprone/refaster/UWildcard.java | 5 +- .../google/errorprone/refaster/Unifier.java | 5 +- .../RequiredAnnotationProcessor.java | 2 +- .../InjectOnFinalFieldPositiveCases.java | 2 +- .../NullablePrimitiveNegativeCases.java | 2 +- .../NullablePrimitivePositiveCases.java | 2 +- ...tentOverloadsPositiveCasesAnnotations.java | 2 +- ...AsyncFunctionReturnsNullNegativeCases.java | 2 +- .../testdata/BadImportPositiveCases.java | 2 +- .../BadImportPositiveCases_expected.java | 2 +- .../UnnecessaryBoxedVariableCases.java | 4 +- ...nnecessaryBoxedVariableCases_expected.java | 2 +- .../errorprone/BugPatternFileGenerator.java | 4 +- test_helpers/pom.xml | 8 +- .../errorprone/CompilationTestHelper.java | 4 +- 179 files changed, 498 insertions(+), 757 deletions(-) diff --git a/check_api/pom.xml b/check_api/pom.xml index 2c7354ba79c..21c7a05fcd5 100644 --- a/check_api/pom.xml +++ b/check_api/pom.xml @@ -44,10 +44,10 @@ ${project.version} - - com.google.code.findbugs - jsr305 - 3.0.2 + + org.jspecify + jspecify + ${jspecify.version} diff --git a/check_api/src/main/java/com/google/errorprone/BaseErrorProneJavaCompiler.java b/check_api/src/main/java/com/google/errorprone/BaseErrorProneJavaCompiler.java index e39065b0420..5c90bdc0001 100644 --- a/check_api/src/main/java/com/google/errorprone/BaseErrorProneJavaCompiler.java +++ b/check_api/src/main/java/com/google/errorprone/BaseErrorProneJavaCompiler.java @@ -34,7 +34,6 @@ import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; -import javax.annotation.Nullable; import javax.lang.model.SourceVersion; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; @@ -42,6 +41,7 @@ import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; +import org.jspecify.annotations.Nullable; /** An Error Prone compiler that implements {@link javax.tools.JavaCompiler}. */ public class BaseErrorProneJavaCompiler implements JavaCompiler { diff --git a/check_api/src/main/java/com/google/errorprone/BugCheckerInfo.java b/check_api/src/main/java/com/google/errorprone/BugCheckerInfo.java index 8f208822dd2..ada95ca6c06 100644 --- a/check_api/src/main/java/com/google/errorprone/BugCheckerInfo.java +++ b/check_api/src/main/java/com/google/errorprone/BugCheckerInfo.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * An accessor for information about a single bug checker, including the metadata in the check's @@ -172,8 +172,7 @@ public BugCheckerInfo withCustomDefaultSeverity(SeverityLevel defaultSeverity) { disableable); } - @Nullable - private static String createLinkUrl(String canonicalName, BugPattern pattern) { + private static @Nullable String createLinkUrl(String canonicalName, BugPattern pattern) { switch (pattern.linkType()) { case AUTOGENERATED: return String.format("https://errorprone.info/bugpattern/%s", canonicalName); diff --git a/check_api/src/main/java/com/google/errorprone/VisitorState.java b/check_api/src/main/java/com/google/errorprone/VisitorState.java index bf347ba67e3..1139bdc9c7a 100644 --- a/check_api/src/main/java/com/google/errorprone/VisitorState.java +++ b/check_api/src/main/java/com/google/errorprone/VisitorState.java @@ -60,8 +60,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import javax.annotation.Nullable; import javax.lang.model.util.Elements; +import org.jspecify.annotations.Nullable; /** * @author alexeagle@google.com (Alex Eagle) @@ -355,16 +355,14 @@ public Name getName(String nameStr) { * @param typeStr the JLS 13.1 binary name of the class, e.g. {@code "java.util.Map$Entry"} * @return the {@link Type}, or null if it cannot be found */ - @Nullable - public Type getTypeFromString(String typeStr) { + public @Nullable Type getTypeFromString(String typeStr) { return sharedState .typeCache .computeIfAbsent(typeStr, key -> Optional.ofNullable(getTypeFromStringInternal(key))) .orElse(null); } - @Nullable - private Type getTypeFromStringInternal(String typeStr) { + private @Nullable Type getTypeFromStringInternal(String typeStr) { validateTypeStr(typeStr); Type primitiveOrVoidType = getPrimitiveOrVoidType(typeStr); if (primitiveOrVoidType != null) { @@ -385,8 +383,7 @@ private Type getTypeFromStringInternal(String typeStr) { * @return the Symbol object, or null if it cannot be found */ // TODO(cushon): deal with binary compat issues and return ClassSymbol - @Nullable - public Symbol getSymbolFromString(String symStr) { + public @Nullable Symbol getSymbolFromString(String symStr) { return getSymbolFromName(binaryNameFromClassname(symStr)); } @@ -404,8 +401,7 @@ public Name binaryNameFromClassname(String className) { * * @param name the name to look up, which must be in binary form (i.e. with $ for nested classes). */ - @Nullable - public ClassSymbol getSymbolFromName(Name name) { + public @Nullable ClassSymbol getSymbolFromName(Name name) { boolean modular = sharedState.modules.getDefaultModule() != getSymtab().noModule; if (!modular) { return getSymbolFromString(getSymtab().noModule, name); @@ -422,8 +418,7 @@ public ClassSymbol getSymbolFromName(Name name) { return null; } - @Nullable - public ClassSymbol getSymbolFromString(ModuleSymbol msym, Name name) { + public @Nullable ClassSymbol getSymbolFromString(ModuleSymbol msym, Name name) { ClassSymbol result = getSymtab().getClass(msym, name); if (result == null || result.kind == Kind.ERR || !result.exists()) { return null; @@ -507,9 +502,8 @@ public Type arrayTypeForType(Type baseType) { * * @return the path, or {@code null} if there is no match */ - @Nullable @SafeVarargs - public final TreePath findPathToEnclosing(Class... classes) { + public final @Nullable TreePath findPathToEnclosing(Class... classes) { TreePath enclosingPath = getPath(); while (enclosingPath != null) { for (Class clazz : classes) { @@ -527,10 +521,10 @@ public final TreePath findPathToEnclosing(Class... classes) { * * @return the node, or {@code null} if there is no match */ - @Nullable - @SuppressWarnings("unchecked") // findPathToEnclosing guarantees that the type is from |classes| + // findPathToEnclosing guarantees that the type is from |classes| + @SuppressWarnings("unchecked") @SafeVarargs - public final T findEnclosing(Class... classes) { + public final @Nullable T findEnclosing(Class... classes) { TreePath pathToEnclosing = findPathToEnclosing(classes); return (pathToEnclosing == null) ? null : (T) pathToEnclosing.getLeaf(); } @@ -540,8 +534,7 @@ public final T findEnclosing(Class... classes) { * * @return the source file as a sequence of characters, or null if it is not available */ - @Nullable - public CharSequence getSourceCode() { + public @Nullable CharSequence getSourceCode() { try { return getPath().getCompilationUnit().getSourceFile().getCharContent(false); } catch (IOException e) { @@ -559,8 +552,7 @@ public CharSequence getSourceCode() { * @return the source code that represents the node, or {@code null} if the source code is * unavailable (e.g. for generated or desugared AST nodes) */ - @Nullable - public String getSourceForNode(Tree tree) { + public @Nullable String getSourceForNode(Tree tree) { int start = ((JCTree) tree).getStartPosition(); int end = getEndPosition(tree); CharSequence source = getSourceCode(); @@ -634,8 +626,7 @@ private static void validateTypeStr(String typeStr) { * Given a string that represents a type, if it's a primitive type (e.g., "int") or "void", return * the corresponding Type, or null otherwise. */ - @Nullable - private Type getPrimitiveOrVoidType(String typeStr) { + private @Nullable Type getPrimitiveOrVoidType(String typeStr) { switch (typeStr) { case "byte": return getSymtab().byteType; diff --git a/check_api/src/main/java/com/google/errorprone/apply/DiffApplier.java b/check_api/src/main/java/com/google/errorprone/apply/DiffApplier.java index 525a07d7a81..d3913a0ef2e 100644 --- a/check_api/src/main/java/com/google/errorprone/apply/DiffApplier.java +++ b/check_api/src/main/java/com/google/errorprone/apply/DiffApplier.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Applier of diffs to Java source code @@ -132,8 +132,7 @@ public void run() { } } - @Nullable - public Future put(Diff diff) { + public @Nullable Future put(Diff diff) { if (refactoredPaths.add(diff.getRelevantFileName())) { runState.incrementAndGet(); return workerService.submit(new Task(diff)); diff --git a/check_api/src/main/java/com/google/errorprone/apply/DiffSupplier.java b/check_api/src/main/java/com/google/errorprone/apply/DiffSupplier.java index fb099fb5db0..6fc0b67c8fb 100644 --- a/check_api/src/main/java/com/google/errorprone/apply/DiffSupplier.java +++ b/check_api/src/main/java/com/google/errorprone/apply/DiffSupplier.java @@ -17,7 +17,7 @@ package com.google.errorprone.apply; import java.io.IOException; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Supplier of file differences. diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/AccessPath.java b/check_api/src/main/java/com/google/errorprone/dataflow/AccessPath.java index 0376f5fa1db..e4af72b7cb2 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/AccessPath.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/AccessPath.java @@ -28,7 +28,6 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import javax.annotation.Nullable; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; @@ -38,6 +37,7 @@ import org.checkerframework.errorprone.dataflow.cfg.node.Node; import org.checkerframework.errorprone.dataflow.cfg.node.VariableDeclarationNode; import org.checkerframework.errorprone.javacutil.TreeUtils; +import org.jspecify.annotations.Nullable; /** * A sequence of field names or autovalue accessors, along with a receiver: either a variable or a @@ -61,8 +61,7 @@ public abstract class AccessPath { /** If present, base of access path is contained Element; if absent, base is `this` */ - @Nullable - public abstract Element base(); + public abstract @Nullable Element base(); public abstract ImmutableList path(); @@ -111,8 +110,7 @@ public static boolean isAutoValueAccessor(Tree tree) { * otherwise (for example, when the receiver of the field access contains an array access or * non-AutoValue method call. */ - @Nullable - public static AccessPath fromFieldAccess(FieldAccessNode fieldAccess) { + public static @Nullable AccessPath fromFieldAccess(FieldAccessNode fieldAccess) { ImmutableList.Builder pathBuilder = ImmutableList.builder(); Tree tree = fieldAccess.getTree(); @@ -161,8 +159,7 @@ public static AccessPath fromVariableDecl(VariableDeclarationNode node) { * Returns an AccessPath representing {@code node} if {@code node} is representable as an access * path and null otherwise */ - @Nullable - public static AccessPath fromNodeIfTrackable(Node node) { + public static @Nullable AccessPath fromNodeIfTrackable(Node node) { if (node instanceof LocalVariableNode) { return fromLocalVariable((LocalVariableNode) node); } else if (node instanceof VariableDeclarationNode) { diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/AccessPathStore.java b/check_api/src/main/java/com/google/errorprone/dataflow/AccessPathStore.java index 10aa81a162a..c27a474ae35 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/AccessPathStore.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/AccessPathStore.java @@ -24,11 +24,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.LinkedHashMap; import java.util.Map; -import javax.annotation.Nullable; import org.checkerframework.errorprone.dataflow.analysis.AbstractValue; import org.checkerframework.errorprone.dataflow.analysis.Store; import org.checkerframework.errorprone.dataflow.cfg.visualize.CFGVisualizer; import org.checkerframework.errorprone.dataflow.expression.JavaExpression; +import org.jspecify.annotations.Nullable; /** * Immutable map from local variables or heap access paths to their {@link AbstractValue} @@ -58,8 +58,7 @@ public static > AccessPathStore empty() { return (AccessPathStore) EMPTY; } - @Nullable - private V getInformation(AccessPath ap) { + private @Nullable V getInformation(AccessPath ap) { return heap().get(checkNotNull(ap)); } diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/ConstantPropagationAnalysis.java b/check_api/src/main/java/com/google/errorprone/dataflow/ConstantPropagationAnalysis.java index ce49e14e376..f70e12005b4 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/ConstantPropagationAnalysis.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/ConstantPropagationAnalysis.java @@ -18,9 +18,9 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.util.Context; -import javax.annotation.Nullable; import org.checkerframework.errorprone.dataflow.constantpropagation.Constant; import org.checkerframework.errorprone.dataflow.constantpropagation.ConstantPropagationTransfer; +import org.jspecify.annotations.Nullable; /** An interface to the constant propagation analysis. */ public final class ConstantPropagationAnalysis { @@ -33,8 +33,7 @@ public final class ConstantPropagationAnalysis { * evaluates to the same numeric value), and null otherwise. Note that returning null does not * necessarily mean the expression is *not* a constant. */ - @Nullable - public static Number numberValue(TreePath exprPath, Context context) { + public static @Nullable Number numberValue(TreePath exprPath, Context context) { Constant val = DataFlow.expressionDataflow(exprPath, context, CONSTANT_PROPAGATION); if (val == null || !val.isConstant()) { return null; diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/DataFlow.java b/check_api/src/main/java/com/google/errorprone/dataflow/DataFlow.java index cd89702173c..31f614d18b2 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/DataFlow.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/DataFlow.java @@ -32,7 +32,6 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Context; -import javax.annotation.Nullable; import javax.annotation.processing.ProcessingEnvironment; import org.checkerframework.errorprone.dataflow.analysis.AbstractValue; import org.checkerframework.errorprone.dataflow.analysis.Analysis; @@ -43,6 +42,7 @@ import org.checkerframework.errorprone.dataflow.cfg.ControlFlowGraph; import org.checkerframework.errorprone.dataflow.cfg.UnderlyingAST; import org.checkerframework.errorprone.dataflow.cfg.builder.CFGBuilder; +import org.jspecify.annotations.Nullable; /** * Provides a wrapper around {@link org.checkerframework.errorprone.dataflow.analysis.Analysis}. @@ -127,8 +127,7 @@ public ControlFlowGraph load(CfgParams key) { }); // TODO(b/158869538): remove once we merge jdk8 specific's with core - @Nullable - private static TreePath findEnclosingMethodOrLambdaOrInitializer(TreePath path) { + private static @Nullable TreePath findEnclosingMethodOrLambdaOrInitializer(TreePath path) { while (path != null) { if (path.getLeaf() instanceof MethodTree) { return path; @@ -201,10 +200,9 @@ public ControlFlowGraph getControlFlowGraph() { * @return dataflow result for the given expression or {@code null} if the expression is not part * of a method, lambda or initializer */ - @Nullable public static < A extends AbstractValue, S extends Store, T extends ForwardTransferFunction> - A expressionDataflow(TreePath exprPath, Context context, T transfer) { + @Nullable A expressionDataflow(TreePath exprPath, Context context, T transfer) { Tree leaf = exprPath.getLeaf(); Preconditions.checkArgument( leaf instanceof ExpressionTree, diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessAnnotations.java b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessAnnotations.java index 0eedff067ba..da65af53475 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessAnnotations.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessAnnotations.java @@ -32,7 +32,6 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -43,6 +42,7 @@ import javax.lang.model.type.IntersectionType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; +import org.jspecify.annotations.Nullable; /** Utilities to extract {@link Nullness} from annotations. */ public class NullnessAnnotations { diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessPropagationTransfer.java b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessPropagationTransfer.java index b4699d5f11e..17b03006fab 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessPropagationTransfer.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/NullnessPropagationTransfer.java @@ -85,7 +85,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import javax.annotation.Nullable; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeVariable; @@ -108,6 +107,7 @@ import org.checkerframework.errorprone.dataflow.cfg.node.SwitchExpressionNode; import org.checkerframework.errorprone.dataflow.cfg.node.TypeCastNode; import org.checkerframework.errorprone.dataflow.cfg.node.VariableDeclarationNode; +import org.jspecify.annotations.Nullable; /** * The {@code TransferFunction} for our nullability analysis. This analysis determines, for all @@ -246,7 +246,7 @@ public boolean test(MethodInfo methodInfo) { private transient CompilationUnitTree compilationUnit; /** Cached local inference results for nullability annotations on type parameters */ - @Nullable private transient InferredNullability inferenceResults; + private transient @Nullable InferredNullability inferenceResults; @Override public AccessPathStore initialStore( @@ -680,8 +680,7 @@ private static boolean hasNonNullConstantValue(LocalVariableNode node) { return false; } - @Nullable - private static ClassAndField tryGetFieldSymbol(Tree tree) { + private static @Nullable ClassAndField tryGetFieldSymbol(Tree tree) { Symbol symbol = tryGetSymbol(tree); if (symbol instanceof VarSymbol) { return ClassAndField.make((VarSymbol) symbol); @@ -689,8 +688,7 @@ private static ClassAndField tryGetFieldSymbol(Tree tree) { return null; } - @Nullable - static ClassAndMethod tryGetMethodSymbol(MethodInvocationTree tree, Types types) { + static @Nullable ClassAndMethod tryGetMethodSymbol(MethodInvocationTree tree, Types types) { Symbol symbol = tryGetSymbol(tree.getMethodSelect()); if (symbol instanceof MethodSymbol) { return ClassAndMethod.make((MethodSymbol) symbol, types); @@ -702,8 +700,7 @@ static ClassAndMethod tryGetMethodSymbol(MethodInvocationTree tree, Types types) * We can't use ASTHelpers here. It's in core, which depends on jdk8, so we can't make jdk8 depend * back on core. */ - @Nullable - private static Symbol tryGetSymbol(Tree tree) { + private static @Nullable Symbol tryGetSymbol(Tree tree) { if (tree instanceof JCIdent) { return ((JCIdent) tree).sym; } @@ -799,8 +796,7 @@ private Nullness returnValueNullness(MethodInvocationNode node, @Nullable ClassA return getInferredNullness(node).orElse(assumedNullness); } - @Nullable - private Nullness fieldInitializerNullnessIfAvailable(ClassAndField accessed) { + private @Nullable Nullness fieldInitializerNullnessIfAvailable(ClassAndField accessed) { if (!traversed.add(accessed.symbol)) { // Circular dependency between initializers results in null. Note static fields can also be // null if they're observed before initialized, but we're ignoring that case for simplicity. diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/TrustingNullnessPropagation.java b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/TrustingNullnessPropagation.java index 4da8c70fdda..8eb55374416 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/TrustingNullnessPropagation.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/TrustingNullnessPropagation.java @@ -20,7 +20,7 @@ import com.google.errorprone.dataflow.AccessPath; import com.google.errorprone.dataflow.AccessPathValues; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Transfer function for {@link TrustingNullnessAnalysis}. It "trusts" annotations, meaning: diff --git a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/inference/NullnessQualifierInference.java b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/inference/NullnessQualifierInference.java index 168734dc7f8..a4121ce3454 100644 --- a/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/inference/NullnessQualifierInference.java +++ b/check_api/src/main/java/com/google/errorprone/dataflow/nullnesspropagation/inference/NullnessQualifierInference.java @@ -63,8 +63,8 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; -import javax.annotation.Nullable; import javax.lang.model.type.TypeVariable; +import org.jspecify.annotations.Nullable; /** * Eagerly traverse one {@code MethodTree} at a time and accumulate constraints between nullness @@ -530,7 +530,6 @@ static TypeAndSymbol create(Type type, @Nullable VarSymbol symbol) { abstract Type type(); - @Nullable - abstract VarSymbol symbol(); + abstract @Nullable VarSymbol symbol(); } } diff --git a/check_api/src/main/java/com/google/errorprone/fixes/AppliedFix.java b/check_api/src/main/java/com/google/errorprone/fixes/AppliedFix.java index 436ea15cf92..0dd685d4738 100644 --- a/check_api/src/main/java/com/google/errorprone/fixes/AppliedFix.java +++ b/check_api/src/main/java/com/google/errorprone/fixes/AppliedFix.java @@ -22,7 +22,7 @@ import com.google.common.collect.Iterables; import com.sun.tools.javac.tree.EndPosTable; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents the corrected source which we think was intended, by applying a Fix. This is used to @@ -60,8 +60,7 @@ public Applier(CharSequence source, EndPosTable endPositions) { * Applies the suggestedFix to the source. Returns null if applying the fix results in no change * to the source, or a change only to imports. */ - @Nullable - public AppliedFix apply(Fix suggestedFix) { + public @Nullable AppliedFix apply(Fix suggestedFix) { // We apply the replacements in ascending order here. Descending is simpler, since applying a // replacement can't change the index for future replacements, but it leads to quadratic // copying behavior as we constantly shift the tail of the file around in our StringBuilder. diff --git a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java index 9dc0c715e7d..2a958d1814a 100644 --- a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java +++ b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java @@ -35,7 +35,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author alexeagle@google.com (Alex Eagle) diff --git a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java index 51e951b817e..5f3d981fc1b 100644 --- a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java +++ b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFixes.java @@ -131,7 +131,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; import java.util.stream.StreamSupport; -import javax.annotation.Nullable; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; @@ -146,13 +145,13 @@ import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.SimpleJavaFileObject; +import org.jspecify.annotations.Nullable; /** Factories for constructing {@link Fix}es. */ public final class SuggestedFixes { /** Parse a modifier token into a {@link Modifier}. */ - @Nullable - private static Modifier getTokModifierKind(ErrorProneToken tok) { + private static @Nullable Modifier getTokModifierKind(ErrorProneToken tok) { switch (tok.kind()) { case PUBLIC: return Modifier.PUBLIC; @@ -1107,8 +1106,7 @@ private static List findAnnotationsTree(Tree tree) { return maybeModifiers == null ? ImmutableList.of() : maybeModifiers.getAnnotations(); } - @Nullable - private static Tree suppressibleNode(TreePath path, VisitorState state) { + private static @Nullable Tree suppressibleNode(TreePath path, VisitorState state) { return StreamSupport.stream(path.spliterator(), false) .filter( tree -> @@ -1502,7 +1500,7 @@ public static String prettyType(Type type, @Nullable VisitorState state) { * #qualifyType}}. */ public static String prettyType( - @Nullable VisitorState state, @Nullable SuggestedFix.Builder existingFix, Type type) { + @Nullable VisitorState state, SuggestedFix.@Nullable Builder existingFix, Type type) { SuggestedFix.Builder fix = existingFix == null ? SuggestedFix.builder() : existingFix; return type.accept( new DefaultTypeVisitor() { diff --git a/check_api/src/main/java/com/google/errorprone/matchers/AnnotationMatcherUtils.java b/check_api/src/main/java/com/google/errorprone/matchers/AnnotationMatcherUtils.java index fd3487f38e9..72b94721125 100644 --- a/check_api/src/main/java/com/google/errorprone/matchers/AnnotationMatcherUtils.java +++ b/check_api/src/main/java/com/google/errorprone/matchers/AnnotationMatcherUtils.java @@ -20,7 +20,7 @@ import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Utilities for matching annotations. @@ -36,8 +36,7 @@ public class AnnotationMatcherUtils { * @param name the name of the argument whose value to get * @return the value of the argument, or null if the argument does not exist */ - @Nullable - public static ExpressionTree getArgument(AnnotationTree annotationTree, String name) { + public static @Nullable ExpressionTree getArgument(AnnotationTree annotationTree, String name) { for (ExpressionTree argumentTree : annotationTree.getArguments()) { if (argumentTree.getKind() != Tree.Kind.ASSIGNMENT) { continue; diff --git a/check_api/src/main/java/com/google/errorprone/matchers/Description.java b/check_api/src/main/java/com/google/errorprone/matchers/Description.java index 76fa22bfa05..41a84f75902 100644 --- a/check_api/src/main/java/com/google/errorprone/matchers/Description.java +++ b/check_api/src/main/java/com/google/errorprone/matchers/Description.java @@ -34,7 +34,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Simple data object containing the information captured about an AST match. Can be printed in a @@ -63,7 +63,7 @@ public class Description { private final String rawMessage; /** The raw link URL for the check. May be null if there is no link. */ - @Nullable private final String linkUrl; + private final @Nullable String linkUrl; /** * A list of fixes to suggest in an error message or use in automated refactoring. Fixes are in @@ -87,8 +87,7 @@ public String getMessage() { } /** Returns a link associated with this finding or null if there is no link. */ - @Nullable - public String getLink() { + public @Nullable String getLink() { return linkUrl; } @@ -135,8 +134,7 @@ public Description applySeverityOverride(SeverityLevel severity) { * Construct the link text to include in the compiler error message. Returns null if there is no * link. */ - @Nullable - private static String linkTextForDiagnostic(String linkUrl) { + private static @Nullable String linkTextForDiagnostic(String linkUrl) { return isNullOrEmpty(linkUrl) ? null : " (see " + linkUrl + ")"; } diff --git a/check_api/src/main/java/com/google/errorprone/matchers/FieldMatchers.java b/check_api/src/main/java/com/google/errorprone/matchers/FieldMatchers.java index 01634d4adab..7875fdb4bd5 100644 --- a/check_api/src/main/java/com/google/errorprone/matchers/FieldMatchers.java +++ b/check_api/src/main/java/com/google/errorprone/matchers/FieldMatchers.java @@ -24,7 +24,7 @@ import com.sun.source.tree.ImportTree; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; // TODO(glorioso): this likely wants to be a fluent interface like MethodMatchers. // Ex: [staticField()|instanceField()] diff --git a/check_api/src/main/java/com/google/errorprone/matchers/method/BaseMethodMatcher.java b/check_api/src/main/java/com/google/errorprone/matchers/method/BaseMethodMatcher.java index 4bd576fda06..b1a356ff056 100644 --- a/check_api/src/main/java/com/google/errorprone/matchers/method/BaseMethodMatcher.java +++ b/check_api/src/main/java/com/google/errorprone/matchers/method/BaseMethodMatcher.java @@ -21,7 +21,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; interface BaseMethodMatcher { @Nullable MatchState match(ExpressionTree tree); diff --git a/check_api/src/main/java/com/google/errorprone/matchers/method/MethodInvocationMatcher.java b/check_api/src/main/java/com/google/errorprone/matchers/method/MethodInvocationMatcher.java index 77e37b64e00..927c633864e 100644 --- a/check_api/src/main/java/com/google/errorprone/matchers/method/MethodInvocationMatcher.java +++ b/check_api/src/main/java/com/google/errorprone/matchers/method/MethodInvocationMatcher.java @@ -41,8 +41,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * The machinery and type definitions necessary to model and compile a single efficient matcher out @@ -314,7 +314,7 @@ private static class Node {} private static class NodeWithDefault { private final Set states; - @Nullable final Set def; + final @Nullable Set def; final SetMultimap mapping; NodeWithDefault(Set states, Set def, SetMultimap mapping) { diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index 84c99e6a853..814fafb2d4b 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -170,11 +170,11 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** This class contains utility methods to work with the javac AST. */ public class ASTHelpers { @@ -224,8 +224,7 @@ public static boolean sameVariable(ExpressionTree expr1, ExpressionTree expr2) { * Gets the symbol declared by a tree. Returns null if {@code tree} does not declare a symbol or * is null. */ - @Nullable - public static Symbol getDeclaredSymbol(Tree tree) { + public static @Nullable Symbol getDeclaredSymbol(Tree tree) { if (tree instanceof PackageTree) { return getSymbol((PackageTree) tree); } @@ -250,8 +249,7 @@ public static Symbol getDeclaredSymbol(Tree tree) { * the wrong type, if {@code tree} is null, or if the symbol cannot be found due to a compilation * error. */ - @Nullable - public static Symbol getSymbol(Tree tree) { + public static @Nullable Symbol getSymbol(Tree tree) { if (tree instanceof AnnotationTree) { return getSymbol(((AnnotationTree) tree).getAnnotationType()); } @@ -440,15 +438,13 @@ public static Stream enclosingElements(Symbol sym) { * Given a TreePath, walks up the tree until it finds a node of the given type. Returns null if no * such node is found. */ - @Nullable - public static T findEnclosingNode(TreePath path, Class klass) { + public static @Nullable T findEnclosingNode(TreePath path, Class klass) { path = findPathFromEnclosingNodeToTopLevel(path, klass); return (path == null) ? null : klass.cast(path.getLeaf()); } /** Finds the enclosing {@link MethodTree}. Returns {@code null} if no such node found. */ - @Nullable - public static MethodTree findEnclosingMethod(VisitorState state) { + public static @Nullable MethodTree findEnclosingMethod(VisitorState state) { for (Tree parent : state.getPath()) { switch (parent.getKind()) { case METHOD: @@ -478,8 +474,8 @@ public static MethodTree findEnclosingMethod(VisitorState state) { * java.lang.String.format() ==> null * } */ - @Nullable - public static ExpressionTree getRootAssignable(MethodInvocationTree methodInvocationTree) { + public static @Nullable ExpressionTree getRootAssignable( + MethodInvocationTree methodInvocationTree) { if (!(methodInvocationTree instanceof JCMethodInvocation)) { throw new IllegalArgumentException( "Expected type to be JCMethodInvocation, but was " + methodInvocationTree.getClass()); @@ -538,8 +534,7 @@ public static Type getReturnType(ExpressionTree expressionTree) { * @param expressionTree the tree to evaluate * @return the result type of this tree or null if unable to resolve it */ - @Nullable - public static Type getResultType(ExpressionTree expressionTree) { + public static @Nullable Type getResultType(ExpressionTree expressionTree) { Type type = ASTHelpers.getType(expressionTree); return type == null ? null : Optional.ofNullable(type.getReturnType()).orElse(type); } @@ -593,8 +588,7 @@ public static Type getReceiverType(ExpressionTree expressionTree) { * aStaticallyImportedMethod() ==> null * } */ - @Nullable - public static ExpressionTree getReceiver(ExpressionTree expressionTree) { + public static @Nullable ExpressionTree getReceiver(ExpressionTree expressionTree) { if (expressionTree instanceof MethodInvocationTree) { ExpressionTree methodSelect = ((MethodInvocationTree) expressionTree).getMethodSelect(); if (methodSelect instanceof IdentifierTree) { @@ -652,8 +646,7 @@ protected ExpressionTree computeNext() { * @param state the VisitorState * @return a list of matched operands, or null if at least one did not match */ - @Nullable - public static List matchBinaryTree( + public static @Nullable List matchBinaryTree( BinaryTree tree, List> matchers, VisitorState state) { ExpressionTree leftOperand = tree.getLeftOperand(); ExpressionTree rightOperand = tree.getRightOperand(); @@ -671,8 +664,7 @@ public static List matchBinaryTree( * Returns the method tree that matches the given symbol within the compilation unit, or null if * none was found. */ - @Nullable - public static MethodTree findMethod(MethodSymbol symbol, VisitorState state) { + public static @Nullable MethodTree findMethod(MethodSymbol symbol, VisitorState state) { return JavacTrees.instance(state.context).getTree(symbol); } @@ -680,15 +672,13 @@ public static MethodTree findMethod(MethodSymbol symbol, VisitorState state) { * Returns the class tree that matches the given symbol within the compilation unit, or null if * none was found. */ - @Nullable - public static ClassTree findClass(ClassSymbol symbol, VisitorState state) { + public static @Nullable ClassTree findClass(ClassSymbol symbol, VisitorState state) { return JavacTrees.instance(state.context).getTree(symbol); } // TODO(ghm): Using a comparison of tsym here appears to be a behaviour change. @SuppressWarnings("TypeEquals") - @Nullable - public static MethodSymbol findSuperMethodInType( + public static @Nullable MethodSymbol findSuperMethodInType( MethodSymbol methodSymbol, Type superType, Types types) { if (methodSymbol.isStatic() || superType.equals(methodSymbol.owner.type)) { return null; @@ -831,7 +821,7 @@ public static boolean isRecord(Symbol symbol) { * inherited from superclasses due to {@code @Inherited}. * * @param annotationClass the binary class name of the annotation (e.g. - * "javax.annotation.Nullable", or "some.package.OuterClassName$InnerClassName") + * "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName") * @return true if the symbol is annotated with given type. */ public static boolean hasAnnotation(Symbol sym, String annotationClass, VisitorState state) { @@ -877,7 +867,7 @@ public static boolean hasAnnotation( * Check for the presence of an annotation, considering annotation inheritance. * * @param annotationClass the binary class name of the annotation (e.g. - * "javax.annotation.Nullable", or "some.package.OuterClassName$InnerClassName") + * "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName") * @return true if the tree is annotated with given type. */ public static boolean hasAnnotation(Tree tree, String annotationClass, VisitorState state) { @@ -1106,9 +1096,9 @@ public static boolean shouldKeep(Tree tree) { * {@link #getAnnotations} (or a direct call to a {@code getAnnotations} method declared on a * specific {@link Tree} subclass) instead. */ - @Nullable @Deprecated - public static T getAnnotation(Tree tree, Class annotationClass) { + public static @Nullable T getAnnotation( + Tree tree, Class annotationClass) { Symbol sym = getSymbol(tree); return sym == null ? null : getAnnotation(sym, annotationClass); } @@ -1121,10 +1111,10 @@ public static T getAnnotation(Tree tree, Class annotat * runtime exception. Instead, operate on {@code sym.getAnnotationMirrors()} to * meta-syntactically inspect the annotation. */ - @Nullable @Deprecated @SuppressWarnings("deprecation") - public static T getAnnotation(Symbol sym, Class annotationClass) { + public static @Nullable T getAnnotation( + Symbol sym, Class annotationClass) { return sym == null ? null : sym.getAnnotation(annotationClass); } @@ -1195,8 +1185,7 @@ public static ImmutableList getConstructors(ClassSymbol classSymbo * Returns the {@code Type} of the given tree, or {@code null} if the type could not be * determined. */ - @Nullable - public static Type getType(@Nullable Tree tree) { + public static @Nullable Type getType(@Nullable Tree tree) { return tree instanceof JCTree ? ((JCTree) tree).type : null; } @@ -1204,14 +1193,12 @@ public static Type getType(@Nullable Tree tree) { * Returns the {@code ClassType} for the given type {@code ClassTree} or {@code null} if the type * could not be determined. */ - @Nullable - public static ClassType getType(@Nullable ClassTree tree) { + public static @Nullable ClassType getType(@Nullable ClassTree tree) { Type type = getType((Tree) tree); return type instanceof ClassType ? (ClassType) type : null; } - @Nullable - public static String getAnnotationName(AnnotationTree tree) { + public static @Nullable String getAnnotationName(AnnotationTree tree) { Symbol sym = getSymbol(tree); return sym == null ? null : sym.name.toString(); } @@ -1234,8 +1221,7 @@ public Tree visitParameterizedType(ParameterizedTypeTree tree, Void unused) { } /** Return the enclosing {@code ClassSymbol} of the given symbol, or {@code null}. */ - @Nullable - public static ClassSymbol enclosingClass(Symbol sym) { + public static @Nullable ClassSymbol enclosingClass(Symbol sym) { // sym.owner is null in the case of module symbols. return sym.owner == null ? null : sym.owner.enclClass(); } @@ -1246,8 +1232,7 @@ public static ClassSymbol enclosingClass(Symbol sym) { *

Prefer this to {@link Symbol#packge}, which throws a {@link NullPointerException} for * symbols that are not contained by a package: https://bugs.openjdk.java.net/browse/JDK-8231911 */ - @Nullable - public static PackageSymbol enclosingPackage(Symbol sym) { + public static @Nullable PackageSymbol enclosingPackage(Symbol sym) { Symbol curr = sym; while (curr != null) { if (curr.getKind().equals(ElementKind.PACKAGE)) { @@ -1279,8 +1264,7 @@ public static Nullness getNullnessValue( } /** Returns the compile-time constant value of a tree if it has one, or {@code null}. */ - @Nullable - public static Object constValue(Tree tree) { + public static @Nullable Object constValue(Tree tree) { if (tree == null) { return null; } @@ -1301,8 +1285,7 @@ public static Object constValue(Tree tree) { } /** Returns the compile-time constant value of a tree if it is of type clazz, or {@code null}. */ - @Nullable - public static T constValue(Tree tree, Class clazz) { + public static @Nullable T constValue(Tree tree, Class clazz) { Object value = constValue(tree); return clazz.isInstance(value) ? clazz.cast(value) : null; } @@ -1362,8 +1345,7 @@ public static boolean isSameType(Type s, Type t, VisitorState state) { } /** Returns the modifiers tree of the given class, method, or variable declaration. */ - @Nullable - public static ModifiersTree getModifiers(Tree tree) { + public static @Nullable ModifiersTree getModifiers(Tree tree) { if (tree instanceof ClassTree) { return ((ClassTree) tree).getModifiers(); } @@ -1462,8 +1444,7 @@ public static boolean isTestNgTestCode(VisitorState state) { } /** Returns an {@link AnnotationTree} with the given simple name, or {@code null}. */ - @Nullable - public static AnnotationTree getAnnotationWithSimpleName( + public static @Nullable AnnotationTree getAnnotationWithSimpleName( List annotations, String name) { for (AnnotationTree annotation : annotations) { if (hasSimpleName(annotation, name)) { @@ -1505,8 +1486,7 @@ private static boolean hasSimpleName(AnnotationTree annotation, String name) { * Returns whether {@code anno} corresponds to a type annotation, or {@code null} if it could not * be determined. */ - @Nullable - public static AnnotationType getAnnotationType( + public static @Nullable AnnotationType getAnnotationType( AnnotationTree anno, @Nullable Symbol target, VisitorState state) { if (target == null) { return null; @@ -1558,8 +1538,7 @@ private static AnnotationType annotationTargetType( * The return value is normalized to always use '/' to separate elements of the path and to always * have a leading '/'. */ - @Nullable - public static String getFileName(CompilationUnitTree tree) { + public static @Nullable String getFileName(CompilationUnitTree tree) { return getFileNameFromUri(tree.getSourceFile().toUri()); } @@ -1569,8 +1548,7 @@ public static String getFileName(CompilationUnitTree tree) { * Extract the filename from the URI, with special handling for jar files. The return value is * normalized to always use '/' to separate elements of the path and to always have a leading '/'. */ - @Nullable - public static String getFileNameFromUri(URI uri) { + public static @Nullable String getFileNameFromUri(URI uri) { if (!uri.getScheme().equals("jar")) { return uri.getPath(); } @@ -1617,8 +1595,7 @@ public static String getFileNameFromUri(URI uri) { * @return a MethodSymbol representing the method symbol resolved from the context of this type, * or {@code null} if the method could not be resolved. */ - @Nullable - public static MethodSymbol resolveExistingMethod( + public static @Nullable MethodSymbol resolveExistingMethod( VisitorState state, TypeSymbol base, Name name, @@ -1734,8 +1711,7 @@ static TargetType create(Type type, TreePath path) { *

JLS * §5.6.2 */ - @Nullable - private static Type binaryNumericPromotion(Type leftType, Type rightType, VisitorState state) { + private static @Nullable Type binaryNumericPromotion( + Type leftType, Type rightType, VisitorState state) { Type unboxedLeft = unboxAndEnsureNumeric(leftType, state); Type unboxedRight = unboxAndEnsureNumeric(rightType, state); Set tags = EnumSet.of(unboxedLeft.getTag(), unboxedRight.getTag()); @@ -1787,8 +1763,7 @@ private static Type unboxAndEnsureNumeric(Type type, VisitorState state) { *

For example, the target type of an assignment expression is the variable's type, and the * target type of a return statement is the enclosing method's type. */ - @Nullable - public static TargetType targetType(VisitorState state) { + public static @Nullable TargetType targetType(VisitorState state) { if (!canHaveTargetType(state.getPath().getLeaf())) { return null; } @@ -1821,11 +1796,10 @@ public static TargetType targetType(VisitorState state) { return TargetType.create(type, parent); } - @Nullable private static final Class CONSTANT_CASE_LABEL_TREE = constantCaseLabelTree(); - @Nullable private static final Class YIELD_TREE = yieldTree(); + private static final @Nullable Class CONSTANT_CASE_LABEL_TREE = constantCaseLabelTree(); + private static final @Nullable Class YIELD_TREE = yieldTree(); - @Nullable - private static Class constantCaseLabelTree() { + private static @Nullable Class constantCaseLabelTree() { try { return Class.forName("com.sun.source.tree.ConstantCaseLabelTree"); } catch (ClassNotFoundException e) { @@ -1833,8 +1807,7 @@ private static Class constantCaseLabelTree() { } } - @Nullable - private static Class yieldTree() { + private static @Nullable Class yieldTree() { try { return Class.forName("com.sun.source.tree.YieldTree"); } catch (ClassNotFoundException e) { @@ -1888,9 +1861,8 @@ private TargetTypeVisitor(ExpressionTree current, VisitorState state, TreePath p this.parent = parent; } - @Nullable @Override - public Type visitArrayAccess(ArrayAccessTree node, Void unused) { + public @Nullable Type visitArrayAccess(ArrayAccessTree node, Void unused) { if (current.equals(node.getIndex())) { return state.getSymtab().intType; } else { @@ -1905,9 +1877,8 @@ public Type visitAssert(AssertTree node, Void unused) { : state.getSymtab().stringType; } - @Nullable @Override - public Type visitAssignment(AssignmentTree tree, Void unused) { + public @Nullable Type visitAssignment(AssignmentTree tree, Void unused) { return getType(tree.getVariable()); } @@ -1916,15 +1887,13 @@ public Type visitAnnotation(AnnotationTree tree, Void unused) { return null; } - @Nullable @Override - public Type visitCase(CaseTree tree, Void unused) { + public @Nullable Type visitCase(CaseTree tree, Void unused) { Tree switchTree = parent.getParentPath().getLeaf(); return getType(getSwitchExpression(switchTree)); } - @Nullable - private static ExpressionTree getSwitchExpression(@Nullable Tree tree) { + private static @Nullable ExpressionTree getSwitchExpression(@Nullable Tree tree) { if (tree == null) { return null; } @@ -1955,9 +1924,8 @@ public Type visitClass(ClassTree node, Void unused) { return null; } - @Nullable @Override - public Type visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { + public @Nullable Type visitCompoundAssignment(CompoundAssignmentTree tree, Void unused) { Type variableType = getType(tree.getVariable()); Type expressionType = getType(tree.getExpression()); Types types = state.getTypes(); @@ -2025,9 +1993,8 @@ public Type visitParenthesized(ParenthesizedTree node, Void unused) { return visit(node.getExpression(), null); } - @Nullable @Override - public Type visitReturn(ReturnTree tree, Void unused) { + public @Nullable Type visitReturn(ReturnTree tree, Void unused) { for (TreePath path = parent; path != null; path = path.getParentPath()) { Tree enclosing = path.getLeaf(); switch (enclosing.getKind()) { @@ -2041,9 +2008,8 @@ public Type visitReturn(ReturnTree tree, Void unused) { throw new AssertionError("return not enclosed by method or lambda"); } - @Nullable @Override - public Type visitSynchronized(SynchronizedTree node, Void unused) { + public @Nullable Type visitSynchronized(SynchronizedTree node, Void unused) { // The null occurs if you've asked for the type of the parentheses around the expression. return Objects.equals(current, node.getExpression()) ? state.getSymtab().objectType : null; } @@ -2058,21 +2024,18 @@ public Type visitTypeCast(TypeCastTree node, Void unused) { return getType(node.getType()); } - @Nullable @Override - public Type visitVariable(VariableTree tree, Void unused) { + public @Nullable Type visitVariable(VariableTree tree, Void unused) { return getType(tree.getType()); } - @Nullable @Override - public Type visitUnary(UnaryTree tree, Void unused) { + public @Nullable Type visitUnary(UnaryTree tree, Void unused) { return getType(tree); } - @Nullable @Override - public Type visitBinary(BinaryTree tree, Void unused) { + public @Nullable Type visitBinary(BinaryTree tree, Void unused) { Type leftType = checkNotNull(getType(tree.getLeftOperand())); Type rightType = checkNotNull(getType(tree.getRightOperand())); switch (tree.getKind()) { @@ -2116,8 +2079,7 @@ && typeIsBoolean(state.getTypes().unboxedTypeOrType(rightType))) { } } - @Nullable - private Type handleEqualityOperator(BinaryTree tree, Type leftType, Type rightType) { + private @Nullable Type handleEqualityOperator(BinaryTree tree, Type leftType, Type rightType) { Type unboxedLeft = checkNotNull(state.getTypes().unboxedTypeOrType(leftType)); Type unboxedRight = checkNotNull(state.getTypes().unboxedTypeOrType(rightType)); @@ -2152,9 +2114,8 @@ private static boolean typeIsBoolean(Type type) { return type.getTag() == TypeTag.BOOLEAN; } - @Nullable @Override - public Type visitConditionalExpression(ConditionalExpressionTree tree, Void unused) { + public @Nullable Type visitConditionalExpression(ConditionalExpressionTree tree, Void unused) { return tree.getCondition().equals(current) ? state.getSymtab().booleanType : getType(tree); } @@ -2173,8 +2134,7 @@ public Type visitMethodInvocation(MethodInvocationTree tree, Void unused) { tree.getArguments(), ASTHelpers.getSymbol(tree), ((JCMethodInvocation) tree).meth.type); } - @Nullable - private Type visitMethodInvocationOrNewClass( + private @Nullable Type visitMethodInvocationOrNewClass( List arguments, MethodSymbol sym, Type type) { int idx = arguments.indexOf(current); if (idx == -1) { @@ -2220,9 +2180,8 @@ public Type visitForLoop(ForLoopTree tree, Void unused) { return getConditionType(tree.getCondition()); } - @Nullable @Override - public Type visitSwitch(SwitchTree node, Void unused) { + public @Nullable Type visitSwitch(SwitchTree node, Void unused) { if (current == node.getExpression()) { return state.getTypes().unboxedTypeOrType(getType(current)); } else { @@ -2230,9 +2189,8 @@ public Type visitSwitch(SwitchTree node, Void unused) { } } - @Nullable @Override - public Type visitNewArray(NewArrayTree node, Void unused) { + public @Nullable Type visitNewArray(NewArrayTree node, Void unused) { if (Objects.equals(node.getType(), current)) { return null; } @@ -2245,9 +2203,8 @@ public Type visitNewArray(NewArrayTree node, Void unused) { return null; } - @Nullable @Override - public Type visitMemberSelect(MemberSelectTree node, Void unused) { + public @Nullable Type visitMemberSelect(MemberSelectTree node, Void unused) { if (current.equals(node.getExpression())) { return ASTHelpers.getType(node.getExpression()); } @@ -2259,8 +2216,7 @@ public Type visitMemberReference(MemberReferenceTree node, Void unused) { return state.getTypes().findDescriptorType(getType(node)).getReturnType(); } - @Nullable - private Type getConditionType(Tree condition) { + private @Nullable Type getConditionType(Tree condition) { if (condition != null && condition.equals(current)) { return state.getSymtab().booleanType; } @@ -2305,8 +2261,7 @@ public static boolean containsComments(Tree tree, VisitorState state) { * aren't containing in a package, unlike {@link Symbol#outermostClass} (see b/123431414). */ // TODO(b/123431414): fix javac and use Symbol.outermostClass insteads - @Nullable - public static ClassSymbol outermostClass(Symbol symbol) { + public static @Nullable ClassSymbol outermostClass(Symbol symbol) { ClassSymbol curr = symbol.enclClass(); while (curr != null && curr.owner != null) { ClassSymbol encl = curr.owner.enclClass(); @@ -2759,8 +2714,7 @@ private static boolean hasMatchingMethods( private static final Method CASE_TREE_GET_LABELS = getCaseTreeGetLabelsMethod(); - @Nullable - private static Method getCaseTreeGetLabelsMethod() { + private static @Nullable Method getCaseTreeGetLabelsMethod() { try { return CaseTree.class.getMethod("getLabels"); } catch (NoSuchMethodException e) { @@ -2799,8 +2753,7 @@ public static Optional getSwitchDefault(SwitchTree switchTre private static final Method CASE_TREE_GET_EXPRESSIONS = getCaseTreeGetExpressionsMethod(); - @Nullable - private static Method getCaseTreeGetExpressionsMethod() { + private static @Nullable Method getCaseTreeGetExpressionsMethod() { try { return CaseTree.class.getMethod("getExpressions"); } catch (NoSuchMethodException e) { @@ -2827,8 +2780,7 @@ public static boolean isRuleKind(CaseTree caseTree) { private static final Method GET_CASE_KIND_METHOD = getGetCaseKindMethod(); - @Nullable - private static Method getGetCaseKindMethod() { + private static @Nullable Method getGetCaseKindMethod() { try { return CaseTree.class.getMethod("getCaseKind"); } catch (NoSuchMethodException e) { diff --git a/check_api/src/main/java/com/google/errorprone/util/ErrorProneScope.java b/check_api/src/main/java/com/google/errorprone/util/ErrorProneScope.java index 0a9bc79197e..6c73cf1c863 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ErrorProneScope.java +++ b/check_api/src/main/java/com/google/errorprone/util/ErrorProneScope.java @@ -27,7 +27,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.function.Predicate; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A compatibility wrapper around {@code com.sun.tools.javac.util.Filter} */ public final class ErrorProneScope { diff --git a/check_api/src/main/java/com/google/errorprone/util/FindIdentifiers.java b/check_api/src/main/java/com/google/errorprone/util/FindIdentifiers.java index b7f0b34a9ef..1f0b82ac45f 100644 --- a/check_api/src/main/java/com/google/errorprone/util/FindIdentifiers.java +++ b/check_api/src/main/java/com/google/errorprone/util/FindIdentifiers.java @@ -73,9 +73,9 @@ import java.util.Set; import java.util.function.BiPredicate; import java.util.stream.StreamSupport; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** A helper class to find all identifiers in scope at a given program point. */ public final class FindIdentifiers { @@ -86,8 +86,7 @@ public static Symbol findIdent(String name, VisitorState state) { } /** Finds a declaration with the given name and type that is in scope at the current location. */ - @Nullable - public static Symbol findIdent(String name, VisitorState state, KindSelector kind) { + public static @Nullable Symbol findIdent(String name, VisitorState state, KindSelector kind) { ClassType enclosingClass = ASTHelpers.getType(getEnclosingClass(state.getPath())); Env env; if (enclosingClass == null || enclosingClass.tsym == null) { @@ -127,8 +126,7 @@ private static Symbol findIdent( return (Symbol) method.invoke(Resolve.instance(state.context), env, state.getName(name), kind); } - @Nullable - private static ClassTree getEnclosingClass(TreePath treePath) { + private static @Nullable ClassTree getEnclosingClass(TreePath treePath) { if (treePath.getLeaf() instanceof ClassTree) { return (ClassTree) treePath.getLeaf(); } diff --git a/core/pom.xml b/core/pom.xml index 55428377101..41f23273342 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -91,6 +91,7 @@ com.google.code.findbugs jsr305 3.0.2 + test @@ -359,7 +360,6 @@ org.jspecify jspecify ${jspecify.version} - test diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractJUnit4InitMethodNotRun.java b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractJUnit4InitMethodNotRun.java index 7d32a5c7628..80606f02e3d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractJUnit4InitMethodNotRun.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractJUnit4InitMethodNotRun.java @@ -37,7 +37,7 @@ import java.io.Serializable; import java.util.List; import javax.lang.model.element.Modifier; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Base class for JUnit4SetUp/TearDown not run. This will take care of the nitty-gritty about diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMockChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMockChecker.java index 3cd390b603c..27ac71e1871 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMockChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMockChecker.java @@ -45,7 +45,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Helper for enforcing Annotations that disallow mocking. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java index 048d3fb15f0..96306a48d29 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java @@ -66,8 +66,8 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * An abstract check for resources that must be closed; used by {@link StreamResourceLeak} and @@ -350,8 +350,7 @@ private static Optional findingWithNoFix() { * Returns the enclosing method of the given visitor state. Returns null if the state is within a * lambda expression or anonymous class. */ - @Nullable - private static MethodTree enclosingMethod(VisitorState state) { + private static @Nullable MethodTree enclosingMethod(VisitorState state) { for (Tree node : state.getPath().getParentPath()) { switch (node.getKind()) { case LAMBDA_EXPRESSION: diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractUseSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractUseSwitch.java index 2dc2641bd2c..ad56ebacb70 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractUseSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractUseSwitch.java @@ -50,7 +50,7 @@ import com.sun.tools.javac.tree.TreeMaker; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Helper for refactoring from if-else chains to switches. */ public abstract class AbstractUseSwitch extends BugChecker implements IfTreeMatcher { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AlreadyChecked.java b/core/src/main/java/com/google/errorprone/bugpatterns/AlreadyChecked.java index c637c0f8183..0d80fb9c67b 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AlreadyChecked.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AlreadyChecked.java @@ -52,7 +52,7 @@ import java.util.HashSet; import java.util.Set; import javax.inject.Inject; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Bugpattern to find conditions which are checked more than once. */ @BugPattern(severity = WARNING, summary = "This condition has already been checked.") diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AlwaysThrows.java b/core/src/main/java/com/google/errorprone/bugpatterns/AlwaysThrows.java index 00a0bc3d87b..300cbbb1208 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AlwaysThrows.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AlwaysThrows.java @@ -48,7 +48,7 @@ import java.util.UUID; import java.util.function.Consumer; import javax.inject.Inject; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern(summary = "Detects calls that will fail at runtime", severity = ERROR) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AnnotateFormatMethod.java b/core/src/main/java/com/google/errorprone/bugpatterns/AnnotateFormatMethod.java index 16a8df71427..518a69c4543 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AnnotateFormatMethod.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AnnotateFormatMethod.java @@ -39,7 +39,7 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Detects occurrences of pairs of parameters being passed straight through to {@link String#format} @@ -119,8 +119,7 @@ private static Optional findParameterWithSymbol( .collect(toOptional()); } - @Nullable - private static VarSymbol asSymbol(ExpressionTree tree) { + private static @Nullable VarSymbol asSymbol(ExpressionTree tree) { Symbol symbol = ASTHelpers.getSymbol(tree); return symbol instanceof VarSymbol ? (VarSymbol) symbol : null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AnnotationPosition.java b/core/src/main/java/com/google/errorprone/bugpatterns/AnnotationPosition.java index 719695adcd0..45e450351ee 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AnnotationPosition.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AnnotationPosition.java @@ -61,9 +61,9 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; +import org.jspecify.annotations.Nullable; /** * Checks annotation positioning, and orphaned Javadocs. @@ -325,8 +325,8 @@ private static String removeJavadoc( return danglingJavadoc.getText(); } - @Nullable - private static ErrorProneComment findOrphanedJavadoc(Name name, List tokens) { + private static @Nullable ErrorProneComment findOrphanedJavadoc( + Name name, List tokens) { for (ErrorProneToken token : tokens) { for (ErrorProneComment comment : token.comments()) { if (comment.getText().startsWith("/**")) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AssertionFailureIgnored.java b/core/src/main/java/com/google/errorprone/bugpatterns/AssertionFailureIgnored.java index 49a28cbf93f..46261739805 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AssertionFailureIgnored.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AssertionFailureIgnored.java @@ -55,7 +55,7 @@ import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -205,8 +205,7 @@ private static Optional catchesType( .findFirst(); } - @Nullable - private static JCTry enclosingTry(VisitorState state) { + private static @Nullable JCTry enclosingTry(VisitorState state) { Tree prev = null; for (Tree parent : state.getPath()) { switch (parent.getKind()) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/BadAnnotationImplementation.java b/core/src/main/java/com/google/errorprone/bugpatterns/BadAnnotationImplementation.java index e15e99dff23..23f034f4a3d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/BadAnnotationImplementation.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/BadAnnotationImplementation.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.util.Name; import java.lang.annotation.Annotation; import java.util.function.Predicate; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Checker that ensures implementations of {@link Annotation} override equals and hashCode. @@ -117,8 +117,7 @@ public Description matchClass(ClassTree classTree, VisitorState state) { return Description.NO_MATCH; } - @Nullable - private static MethodSymbol getMatchingMethod( + private static @Nullable MethodSymbol getMatchingMethod( Type type, Name name, Predicate predicate) { Scope scope = type.tsym.members(); for (Symbol sym : scope.getSymbolsByName(name)) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/BoxedPrimitiveConstructor.java b/core/src/main/java/com/google/errorprone/bugpatterns/BoxedPrimitiveConstructor.java index 345826d8532..9c9727b7260 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/BoxedPrimitiveConstructor.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/BoxedPrimitiveConstructor.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -269,8 +269,7 @@ private static String literalFix(boolean value, boolean autoboxFix) { return value ? "Boolean.TRUE" : "Boolean.FALSE"; } - @Nullable - private static Object literalValue(Tree arg) { + private static @Nullable Object literalValue(Tree arg) { if (!(arg instanceof LiteralTree)) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/CatchingUnchecked.java b/core/src/main/java/com/google/errorprone/bugpatterns/CatchingUnchecked.java index 46d8204d3f8..4a0f2413dba 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/CatchingUnchecked.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/CatchingUnchecked.java @@ -36,7 +36,7 @@ import com.sun.tools.javac.code.Type.UnionClassType; import java.util.HashSet; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Flags code which catches {@link RuntimeException}s under the guise of catching {@link Exception}. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ChainedAssertionLosesContext.java b/core/src/main/java/com/google/errorprone/bugpatterns/ChainedAssertionLosesContext.java index 226c050ee27..26300cb2efb 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ChainedAssertionLosesContext.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ChainedAssertionLosesContext.java @@ -51,7 +51,7 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Type; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Identifies calls to {@code assertThat} and similar methods inside the implementation of a {@code @@ -151,8 +151,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState * {@code check()}, which is part of the expression {@code check().that(...)}. But sometimes there * is an intervening call to {@code withMessage}, {@code about}, or both. */ - @Nullable - static MethodInvocationTree findThatCall(VisitorState state) { + static @Nullable MethodInvocationTree findThatCall(VisitorState state) { TreePath path = state.getPath(); /* * Each iteration walks 1 method call up the tree, but it's actually 2 steps in the tree because @@ -186,8 +185,7 @@ static FactoryMethodName create(String clazz, String method) { return new AutoValue_ChainedAssertionLosesContext_FactoryMethodName(clazz, method); } - @Nullable - static FactoryMethodName tryCreate(MethodSymbol symbol) { + static @Nullable FactoryMethodName tryCreate(MethodSymbol symbol) { return symbol.params.isEmpty() ? create(symbol.owner.getQualifiedName().toString(), symbol.getSimpleName().toString()) : null; @@ -198,8 +196,7 @@ static FactoryMethodName tryCreate(MethodSymbol symbol) { abstract String method(); } - @Nullable - private static FactoryMethodName tryFindFactory( + private static @Nullable FactoryMethodName tryFindFactory( MethodInvocationTree assertThatCall, VisitorState state) { MethodSymbol assertThatSymbol = getSymbol(assertThatCall); /* @@ -258,8 +255,7 @@ private static boolean inInstanceMethodOfSubjectImplementation(VisitorState stat getDeclaredSymbol(enclosingClass).type, COM_GOOGLE_COMMON_TRUTH_SUBJECT.get(state), state); } - @Nullable - private static String findThatCallAndMakeCheckDescription(VisitorState state) { + private static @Nullable String findThatCallAndMakeCheckDescription(VisitorState state) { MethodInvocationTree thatCall = findThatCall(state); if (thatCall == null) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/CheckReturnValue.java b/core/src/main/java/com/google/errorprone/bugpatterns/CheckReturnValue.java index f54482815f3..d2780828adb 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/CheckReturnValue.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/CheckReturnValue.java @@ -91,7 +91,7 @@ import java.util.stream.Stream; import javax.inject.Inject; import javax.lang.model.element.ElementKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author eaftan@google.com (Eddie Aftandilian) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ComplexBooleanConstant.java b/core/src/main/java/com/google/errorprone/bugpatterns/ComplexBooleanConstant.java index 25110d00998..1edd0ecbe3a 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ComplexBooleanConstant.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ComplexBooleanConstant.java @@ -29,7 +29,7 @@ import com.sun.source.util.SimpleTreeVisitor; import com.sun.tools.javac.tree.JCTree.JCLiteral; import java.util.Objects; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Sumit Bhagwani (bhagwani@google.com) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ConstantOverflow.java b/core/src/main/java/com/google/errorprone/bugpatterns/ConstantOverflow.java index 73b26cb7c14..6d58c833688 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ConstantOverflow.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ConstantOverflow.java @@ -47,8 +47,8 @@ import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; -import javax.annotation.Nullable; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern(summary = "Compile-time constant expression overflows", severity = ERROR) @@ -80,8 +80,7 @@ public Description matchBinary(BinaryTree tree, VisitorState state) { /** * If the left operand of an int binary expression is an int literal, suggest making it a long. */ - @Nullable - private static Fix longFix(ExpressionTree expr, VisitorState state) { + private static @Nullable Fix longFix(ExpressionTree expr, VisitorState state) { BinaryTree binExpr = null; while (expr instanceof BinaryTree) { binExpr = (BinaryTree) expr; @@ -109,9 +108,8 @@ private static Fix longFix(ExpressionTree expr, VisitorState state) { private static final SimpleTreeVisitor CONSTANT_VISITOR = new SimpleTreeVisitor() { - @Nullable @Override - public Number visitConditionalExpression(ConditionalExpressionTree node, Void p) { + public @Nullable Number visitConditionalExpression(ConditionalExpressionTree node, Void p) { Number ifTrue = node.getTrueExpression().accept(this, null); Number ifFalse = node.getFalseExpression().accept(this, null); Boolean condition = ASTHelpers.constValue(node.getCondition(), Boolean.class); @@ -126,9 +124,8 @@ public Number visitParenthesized(ParenthesizedTree node, Void p) { return node.getExpression().accept(this, null); } - @Nullable @Override - public Number visitUnary(UnaryTree node, Void p) { + public @Nullable Number visitUnary(UnaryTree node, Void p) { Number value = node.getExpression().accept(this, null); if (value == null) { return null; @@ -140,9 +137,8 @@ public Number visitUnary(UnaryTree node, Void p) { } } - @Nullable @Override - public Number visitBinary(BinaryTree node, Void p) { + public @Nullable Number visitBinary(BinaryTree node, Void p) { Number lhs = node.getLeftOperand().accept(this, null); Number rhs = node.getRightOperand().accept(this, null); if (lhs == null || rhs == null) { @@ -172,9 +168,8 @@ public Number visitBinary(BinaryTree node, Void p) { } } - @Nullable @Override - public Number visitTypeCast(TypeCastTree node, Void p) { + public @Nullable Number visitTypeCast(TypeCastTree node, Void p) { Number value = node.getExpression().accept(this, null); if (value == null) { return null; @@ -202,8 +197,7 @@ public Number visitLiteral(LiteralTree node, Void unused) { } }; - @Nullable - private static Long unop(Kind kind, long value) { + private static @Nullable Long unop(Kind kind, long value) { switch (kind) { case UNARY_PLUS: return +value; @@ -216,8 +210,7 @@ private static Long unop(Kind kind, long value) { } } - @Nullable - private static Integer unop(Kind kind, int value) { + private static @Nullable Integer unop(Kind kind, int value) { switch (kind) { case UNARY_PLUS: return +value; @@ -230,8 +223,7 @@ private static Integer unop(Kind kind, int value) { } } - @Nullable - static Long binop(Kind kind, long lhs, long rhs) { + static @Nullable Long binop(Kind kind, long lhs, long rhs) { switch (kind) { case MULTIPLY: return LongMath.checkedMultiply(lhs, rhs); @@ -260,8 +252,7 @@ static Long binop(Kind kind, long lhs, long rhs) { } } - @Nullable - static Integer binop(Kind kind, int lhs, int rhs) { + static @Nullable Integer binop(Kind kind, int lhs, int rhs) { switch (kind) { case MULTIPLY: return IntMath.checkedMultiply(lhs, rhs); @@ -290,8 +281,7 @@ static Integer binop(Kind kind, int lhs, int rhs) { } } - @Nullable - private static Number cast(TypeKind kind, Number value) { + private static @Nullable Number cast(TypeKind kind, Number value) { switch (kind) { case SHORT: return value.shortValue(); @@ -308,8 +298,7 @@ private static Number cast(TypeKind kind, Number value) { } } - @Nullable - private static Number getIntegralConstant(Tree node) { + private static @Nullable Number getIntegralConstant(Tree node) { Number number = ASTHelpers.constValue(node, Number.class); if (number instanceof Integer || number instanceof Long) { return number; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ConstantPatternCompile.java b/core/src/main/java/com/google/errorprone/bugpatterns/ConstantPatternCompile.java index 24805c6cd4b..74495a53baf 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ConstantPatternCompile.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ConstantPatternCompile.java @@ -62,7 +62,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Flags variables initialized with {@link java.util.regex.Pattern#compile(String)} calls that could diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/DeduplicateConstants.java b/core/src/main/java/com/google/errorprone/bugpatterns/DeduplicateConstants.java index 19e4a1dfe14..6c4348bc9d9 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/DeduplicateConstants.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/DeduplicateConstants.java @@ -40,7 +40,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A checker that suggests deduplicating literals with existing constant variables. @@ -76,8 +76,7 @@ Scope enter() { } /** Returns an in-scope constant variable with the given value. */ - @Nullable - public VarSymbol get(String value) { + public @Nullable VarSymbol get(String value) { VarSymbol sym = getInternal(value); if (sym == null) { return null; @@ -88,8 +87,7 @@ public VarSymbol get(String value) { return sym; } - @Nullable - private VarSymbol getInternal(String value) { + private @Nullable VarSymbol getInternal(String value) { VarSymbol sym = values.get(value); if (sym != null) { return sym; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateDateFormatField.java b/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateDateFormatField.java index d641a16e2fe..66e01253cdc 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateDateFormatField.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/DuplicateDateFormatField.java @@ -30,7 +30,7 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Flag DateFormats which use the same field more than once. */ @BugPattern(summary = "Reuse of DateFormat fields is most likely unintentional", severity = WARNING) @@ -44,7 +44,7 @@ private static class PatternCounter implements DateFormatConsumer { private final Set seen = new HashSet<>(); private final Set duplicates = new HashSet<>(); - @Nullable private Character prev = null; + private @Nullable Character prev = null; private int optionalGroupDepth = 0; @Override diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/EqualsHashCode.java b/core/src/main/java/com/google/errorprone/bugpatterns/EqualsHashCode.java index 2ea8536edfc..4bb5d5ce6c7 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/EqualsHashCode.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/EqualsHashCode.java @@ -38,8 +38,8 @@ import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Type; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * Classes that override {@link Object#equals} should also override {@link Object#hashCode}. @@ -73,8 +73,7 @@ public Description matchClass(ClassTree classTree, VisitorState state) { *

  • there is no additional method with name matching {@code expectedNoArgMethod} * */ - @Nullable - private static MethodTree checkMethodPresence(ClassTree classTree, VisitorState state) { + private static @Nullable MethodTree checkMethodPresence(ClassTree classTree, VisitorState state) { TypeSymbol symbol = ASTHelpers.getSymbol(classTree); if (symbol.getKind() != ElementKind.CLASS) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/EqualsNaN.java b/core/src/main/java/com/google/errorprone/bugpatterns/EqualsNaN.java index 7de93caed52..1219537154f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/EqualsNaN.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/EqualsNaN.java @@ -29,7 +29,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCExpression; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author lowasser@google.com (Louis Wasserman) @@ -77,8 +77,7 @@ private static CharSequence toString(JCTree tree, VisitorState state) { return (source == null) ? tree.toString() : source; } - @Nullable - private static String matchNaN(ExpressionTree tree) { + private static @Nullable String matchNaN(ExpressionTree tree) { Symbol sym = ASTHelpers.getSymbol(tree); if (sym != null && sym.owner != null diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ExpectedExceptionChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/ExpectedExceptionChecker.java index dd80d504c18..7ba994e3741 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ExpectedExceptionChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ExpectedExceptionChecker.java @@ -65,7 +65,7 @@ import java.util.Deque; import java.util.List; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern(summary = "Prefer assertThrows to ExpectedException", severity = WARNING) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ForEachIterable.java b/core/src/main/java/com/google/errorprone/bugpatterns/ForEachIterable.java index b655a745e9b..63d864b2b9c 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ForEachIterable.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ForEachIterable.java @@ -50,7 +50,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeTag; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern(summary = "This loop can be replaced with an enhanced for loop.", severity = SUGGESTION) @@ -196,8 +196,7 @@ public Void visitIdentifier(IdentifierTree identifierTree, Void unused) { return uses.build(); } - @Nullable - private static VariableTree existingVariable( + private static @Nullable VariableTree existingVariable( VarSymbol varSymbol, StatementTree body, VisitorState state) { if (!(body instanceof BlockTree)) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ForOverrideChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/ForOverrideChecker.java index e25438fcfaf..f20ae10e09f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ForOverrideChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ForOverrideChecker.java @@ -44,8 +44,8 @@ import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Type; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** * Verifies that methods marked {@link com.google.errorprone.annotations.ForOverride} are only @@ -147,8 +147,7 @@ public Description matchMethod(MethodTree tree, VisitorState state) { *

    By 'direct', we mean that if the leaf is part of a field initializer of a class, then it is * considered to not be part of any method. */ - @Nullable - private static MethodTree findDirectMethod(TreePath path) { + private static @Nullable MethodTree findDirectMethod(TreePath path) { while (true) { path = path.getParentPath(); if (path != null) { @@ -205,8 +204,7 @@ private static ImmutableList getOverriddenMethods( } /** Get the outermost class/interface/enum of an element, or null if none. */ - @Nullable - private static Type getOutermostClass(VisitorState state) { + private static @Nullable Type getOutermostClass(VisitorState state) { return findLast( stream(state.getPath()) .filter(t -> t instanceof ClassTree) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ImplementAssertionWithChaining.java b/core/src/main/java/com/google/errorprone/bugpatterns/ImplementAssertionWithChaining.java index ec8f5c1a2ca..c722309ca47 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ImplementAssertionWithChaining.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ImplementAssertionWithChaining.java @@ -55,7 +55,7 @@ import java.util.Deque; import java.util.List; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Migrates Truth subjects from a manual "test and fail" approach to one using {@code @@ -115,8 +115,7 @@ public Description matchIf(IfTree ifTree, VisitorState state) { state.getSourceForNode(actualAndExpected.get(1))))); } - @Nullable - private static ImmutableList findActualAndExpected( + private static @Nullable ImmutableList findActualAndExpected( ExpressionTree condition, VisitorState state) { /* * Note that all these look "backward": If the code is "if (foo == bar) { fail }," then the @@ -135,8 +134,7 @@ private static ImmutableList findActualAndExpected( } } - @Nullable - private static ImmutableList findActualAndExpectedForPossibleEqualsCall( + private static @Nullable ImmutableList findActualAndExpectedForPossibleEqualsCall( ExpressionTree possiblyEqualsCall, VisitorState state) { if (!EQUALS_LIKE_METHOD.matches(possiblyEqualsCall, state)) { return null; @@ -151,8 +149,7 @@ private static ImmutableList findActualAndExpectedForPossibleEqu getOnlyElement(args)); } - @Nullable - private static ImmutableList findActualAndExpectedForBinaryOp( + private static @Nullable ImmutableList findActualAndExpectedForBinaryOp( BinaryTree binaryTree, VisitorState state) { /* * It's actually enough for *either* to be a primitive, thanks to autounboxing (and enough for @@ -214,8 +211,7 @@ private static boolean isCallToFail(StatementTree then, VisitorState state) { * that case, they appear "backward" as we walk the tree (i.e., bar, foo), so we add each one to * the beginning of the list as we go. */ - @Nullable - static String makeCheckDescription(ExpressionTree actual, VisitorState state) { + static @Nullable String makeCheckDescription(ExpressionTree actual, VisitorState state) { /* * This conveniently also acts as a check that the actual and expected values aren't backward, * since the actual value is almost always an invocation on actual() and the expected value is diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ImpossibleNullComparison.java b/core/src/main/java/com/google/errorprone/bugpatterns/ImpossibleNullComparison.java index 8307cc6c725..55b938d35d1 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ImpossibleNullComparison.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ImpossibleNullComparison.java @@ -68,8 +68,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import javax.annotation.Nullable; import javax.inject.Inject; +import org.jspecify.annotations.Nullable; /** Matches comparison of proto fields to {@code null}. */ @BugPattern( @@ -193,9 +193,9 @@ public Void visitVariable(VariableTree variable, Void unused) { private Optional getInitializer(ExpressionTree tree) { return Optional.ofNullable( new SimpleTreeVisitor() { - @Nullable @Override - public ExpressionTree visitMethodInvocation(MethodInvocationTree node, Void unused) { + public @Nullable ExpressionTree visitMethodInvocation( + MethodInvocationTree node, Void unused) { return PROTO_RECEIVER.matches(node, state) ? node : null; } @@ -270,8 +270,7 @@ private Optional getFixer(ExpressionTree tree, VisitorState state) { .findFirst(); } - @Nullable - private ExpressionTree getEffectiveTree(ExpressionTree tree) { + private @Nullable ExpressionTree getEffectiveTree(ExpressionTree tree) { return tree.getKind() == Kind.IDENTIFIER ? effectivelyFinalValues.getOrDefault(ASTHelpers.getSymbol(tree), tree) : tree; @@ -322,9 +321,8 @@ private interface Fixer { private enum GetterTypes { OPTIONAL_GET { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!OPTIONAL_GET_MATCHER.matches(tree, state)) { return null; } @@ -334,9 +332,8 @@ Fixer match(ExpressionTree tree, VisitorState state) { } }, GUAVA_OPTIONAL_GET { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!GUAVA_OPTIONAL_GET_MATCHER.matches(tree, state)) { return null; } @@ -345,9 +342,8 @@ Fixer match(ExpressionTree tree, VisitorState state) { } }, MULTIMAP_GET { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!MULTIMAP_GET_MATCHER.matches(tree, state)) { return null; } @@ -362,9 +358,8 @@ Fixer match(ExpressionTree tree, VisitorState state) { } }, TABLE_ROW_GET { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!TABLE_ROW_MATCHER.matches(tree, state)) { return null; } @@ -379,9 +374,8 @@ Fixer match(ExpressionTree tree, VisitorState state) { } }, TABLE_COLUMN_GET { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!TABLE_COLUMN_MATCHER.matches(tree, state)) { return null; } @@ -396,17 +390,15 @@ Fixer match(ExpressionTree tree, VisitorState state) { } }, PRIMITIVE { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { var type = getType(tree); return type != null && type.isPrimitive() ? GetterTypes::emptyFix : null; } }, VALUE_OF { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!NON_NULL_VALUE_OF.matches(tree, state)) { return null; } @@ -416,9 +408,8 @@ Fixer match(ExpressionTree tree, VisitorState state) { }, /** {@code proto.getFoo()} */ SCALAR { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!PROTO_RECEIVER.matches(tree, state)) { return null; } @@ -465,9 +456,8 @@ private String replaceLast(String text, String pattern, String replacement) { }, /** {@code proto.getRepeatedFoo(index)} */ VECTOR_INDEXED { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!PROTO_RECEIVER.matches(tree, state)) { return null; } @@ -499,9 +489,8 @@ private String generateFix( }, /** {@code proto.getRepeatedFooList()} */ VECTOR { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!PROTO_RECEIVER.matches(tree, state)) { return null; } @@ -529,9 +518,8 @@ private String generateFix( }, /** {@code proto.getField(f)} or {@code proto.getExtension(outer, extension)}; */ EXTENSION_METHOD { - @Nullable @Override - Fixer match(ExpressionTree tree, VisitorState state) { + @Nullable Fixer match(ExpressionTree tree, VisitorState state) { if (!PROTO_RECEIVER.matches(tree, state)) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/InstanceOfAndCastMatchWrongType.java b/core/src/main/java/com/google/errorprone/bugpatterns/InstanceOfAndCastMatchWrongType.java index 42f503085f2..1bec8f914b2 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/InstanceOfAndCastMatchWrongType.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/InstanceOfAndCastMatchWrongType.java @@ -43,7 +43,7 @@ import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Types; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author sulku@google.com (Marsela Sulku) @@ -147,8 +147,7 @@ static class TreeScannerInstanceOfWrongType extends TreeScannerlong literal with a lower-case ell for a suffix (e.g. 234l @@ -64,8 +64,7 @@ public boolean matches(LiteralTree literalTree, VisitorState state) { * Extracts the long literal corresponding to a given {@link LiteralTree} node from the source * code as a string. Returns null if the source code is not available. */ - @Nullable - private static String getLongLiteral(LiteralTree literalTree, VisitorState state) { + private static @Nullable String getLongLiteral(LiteralTree literalTree, VisitorState state) { JCLiteral longLiteral = (JCLiteral) literalTree; CharSequence sourceFile = state.getSourceCode(); if (sourceFile == null) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/MissingTestCall.java b/core/src/main/java/com/google/errorprone/bugpatterns/MissingTestCall.java index fbbe69dca54..a6d102843c0 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/MissingTestCall.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/MissingTestCall.java @@ -40,8 +40,8 @@ import com.sun.tools.javac.code.Symbol; import java.util.HashSet; import java.util.Set; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * Matches test helpers which require a terminating method to be called. @@ -123,8 +123,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { .orElse(NO_MATCH); } - @Nullable - private static ExpressionTree getUltimateReceiver(ExpressionTree tree) { + private static @Nullable ExpressionTree getUltimateReceiver(ExpressionTree tree) { return findLast(streamReceivers(tree)).orElse(null); } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ModifySourceCollectionInStream.java b/core/src/main/java/com/google/errorprone/bugpatterns/ModifySourceCollectionInStream.java index 02fd93a71a4..76ae165d5ca 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ModifySourceCollectionInStream.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ModifySourceCollectionInStream.java @@ -34,7 +34,7 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Identify the backing collection source of a stream and reports if the source is mutated during diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NarrowingCompoundAssignment.java b/core/src/main/java/com/google/errorprone/bugpatterns/NarrowingCompoundAssignment.java index b1fdfc12bc6..9876cffdfcf 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/NarrowingCompoundAssignment.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NarrowingCompoundAssignment.java @@ -37,7 +37,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree.JCBinary; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -121,8 +121,7 @@ public Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorS } /** Classifies bad casts. */ - @Nullable - private static String identifyBadCast(Type lhs, Type rhs, Types types) { + private static @Nullable String identifyBadCast(Type lhs, Type rhs, Types types) { if (!lhs.isPrimitive()) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NonCanonicalType.java b/core/src/main/java/com/google/errorprone/bugpatterns/NonCanonicalType.java index d679e69b4ba..1de0b2134f8 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/NonCanonicalType.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NonCanonicalType.java @@ -37,7 +37,7 @@ import com.sun.tools.javac.code.Symbol.TypeSymbol; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Flags types being referred to by their non-canonical name. */ @BugPattern( @@ -80,8 +80,7 @@ public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) .build(); } - @Nullable - private static String canonicalName(MemberSelectTree tree) { + private static @Nullable String canonicalName(MemberSelectTree tree) { Symbol sym = getSymbol(tree); if (sym == null) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/NullOptional.java b/core/src/main/java/com/google/errorprone/bugpatterns/NullOptional.java index d35400bf5e7..91c3f414470 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/NullOptional.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/NullOptional.java @@ -42,7 +42,7 @@ import com.sun.tools.javac.code.Type.ArrayType; import java.util.Iterator; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Flags passing literal null to {@code Optional}-accepting APIs. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/RedundantOverride.java b/core/src/main/java/com/google/errorprone/bugpatterns/RedundantOverride.java index 4d085d6294f..89e0d5bb77d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/RedundantOverride.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/RedundantOverride.java @@ -46,8 +46,8 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import java.util.Objects; import java.util.Optional; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** Removes overrides which purely pass through to the method in the super class. */ @BugPattern( @@ -130,8 +130,7 @@ public Description matchMethod(MethodTree tree, VisitorState state) { return describeMatch(tree, SuggestedFix.delete(tree)); } - @Nullable - private static MethodInvocationTree getSingleInvocation(StatementTree statement) { + private static @Nullable MethodInvocationTree getSingleInvocation(StatementTree statement) { return statement.accept( new SimpleTreeVisitor() { @Override diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/RedundantSetterCall.java b/core/src/main/java/com/google/errorprone/bugpatterns/RedundantSetterCall.java index 4b08e933309..3f7e4103f27 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/RedundantSetterCall.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/RedundantSetterCall.java @@ -61,7 +61,7 @@ import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A BugPattern; see the summary. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/RemoveUnusedImports.java b/core/src/main/java/com/google/errorprone/bugpatterns/RemoveUnusedImports.java index 9bdba892ae2..5665c64a501 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/RemoveUnusedImports.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/RemoveUnusedImports.java @@ -46,7 +46,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.DCTree.DCReference; import java.util.LinkedHashSet; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author gak@google.com (Gregory Kick) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/RestrictedApiChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/RestrictedApiChecker.java index f04fafa0b72..3490497d2dd 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/RestrictedApiChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/RestrictedApiChecker.java @@ -59,7 +59,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Check for non-allowlisted callers to RestrictedApiChecker. */ @BugPattern( @@ -194,8 +194,8 @@ private Description checkMethodUse( .orElse(NO_MATCH); } - @Nullable - private static Attribute.Compound getRestrictedApiAnnotation(Symbol sym, VisitorState state) { + private static Attribute.@Nullable Compound getRestrictedApiAnnotation( + Symbol sym, VisitorState state) { if (sym == null) { return null; } @@ -203,7 +203,7 @@ private static Attribute.Compound getRestrictedApiAnnotation(Symbol sym, Visitor } private Description checkRestriction( - @Nullable Attribute.Compound attribute, Tree where, VisitorState state) { + Attribute.@Nullable Compound attribute, Tree where, VisitorState state) { if (attribute == null) { return NO_MATCH; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/SameNameButDifferent.java b/core/src/main/java/com/google/errorprone/bugpatterns/SameNameButDifferent.java index 9669aded089..51438b859f8 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/SameNameButDifferent.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/SameNameButDifferent.java @@ -52,7 +52,7 @@ import javax.inject.Inject; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Looks for types being shadowed by other types in a way that may be confusing. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/SelfAssignment.java b/core/src/main/java/com/google/errorprone/bugpatterns/SelfAssignment.java index 8d0175874b6..5e35e376800 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/SelfAssignment.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/SelfAssignment.java @@ -49,7 +49,7 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * TODO(eaftan): Consider cases where the parent is not a statement or there is no parent? diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/SelfEquals.java b/core/src/main/java/com/google/errorprone/bugpatterns/SelfEquals.java index d53df09abca..51b553ac780 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/SelfEquals.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/SelfEquals.java @@ -46,7 +46,7 @@ import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author bhagwani@google.com (Sumit Bhagwani) @@ -93,8 +93,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return description.build(); } - @Nullable - protected static SuggestedFix fieldFix(Tree toReplace, VisitorState state) { + protected static @Nullable SuggestedFix fieldFix(Tree toReplace, VisitorState state) { TreePath path = state.getPath(); while (path != null && path.getLeaf().getKind() != Kind.CLASS diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StaticImports.java b/core/src/main/java/com/google/errorprone/bugpatterns/StaticImports.java index 90d20ebf61f..45b84a1f79d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StaticImports.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StaticImports.java @@ -34,7 +34,7 @@ import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Name; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Logic for inspecting static imports used by {@link NonCanonicalStaticImport}, {@link @@ -91,8 +91,7 @@ private static StaticImportInfo create( * {@code null} otherwise, e.g. because the import is non-static, or an on-demand import, or * statically imports a field or method. */ - @Nullable - public static StaticImportInfo tryCreate(ImportTree tree, VisitorState state) { + public static @Nullable StaticImportInfo tryCreate(ImportTree tree, VisitorState state) { if (!tree.isStatic()) { return null; } @@ -109,8 +108,7 @@ public static StaticImportInfo tryCreate(ImportTree tree, VisitorState state) { return tryCreate(access, state); } - @Nullable - public static StaticImportInfo tryCreate(MemberSelectTree access, VisitorState state) { + public static @Nullable StaticImportInfo tryCreate(MemberSelectTree access, VisitorState state) { Name identifier = (Name) access.getIdentifier(); Symbol importedType = getSymbol(access.getExpression()); if (importedType == null) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StringSplitter.java b/core/src/main/java/com/google/errorprone/bugpatterns/StringSplitter.java index 5caf54ec10f..3b71336f016 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StringSplitter.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StringSplitter.java @@ -56,7 +56,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern(summary = "String.split(String) has surprising behavior", severity = WARNING) @@ -294,8 +294,7 @@ private static SuggestedFix.Builder replaceWithSplitter( throw new AssertionError(receiver); } - @Nullable - private static TreePath findEnclosing(VisitorState state) { + private static @Nullable TreePath findEnclosing(VisitorState state) { for (TreePath path = state.getPath(); path != null; path = path.getParentPath()) { switch (path.getLeaf().getKind()) { case METHOD: diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java b/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java index bf114cc9711..4bd82b4c43b 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/SwitchDefault.java @@ -34,7 +34,7 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/TestExceptionChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/TestExceptionChecker.java index ccb646fcc9f..6f9eb72f103 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/TestExceptionChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/TestExceptionChecker.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.tree.JCTree.Tag; import java.util.Collection; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -114,8 +114,7 @@ private static SuggestedFix buildFix( * Searches the annotation list for {@code @Test(expected=...)}. If found, deletes the exception * attribute from the annotation, and returns its value. */ - @Nullable - private static JCExpression deleteExpectedException( + private static @Nullable JCExpression deleteExpectedException( SuggestedFix.Builder fix, List annotations, VisitorState state) { Type testAnnotation = ORG_JUNIT_TEST.get(state); for (JCAnnotation annotationTree : annotations) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/TruthAssertExpected.java b/core/src/main/java/com/google/errorprone/bugpatterns/TruthAssertExpected.java index a974b30ad47..ad72c3c2d68 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/TruthAssertExpected.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/TruthAssertExpected.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Type; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Detects usages of Truth assertions with the expected and actual values reversed. @@ -132,9 +132,8 @@ public List visitNewClass(NewClassTree node, Void unus return node.getArguments(); } - @Nullable @Override - public List visitMethodInvocation( + public @Nullable List visitMethodInvocation( MethodInvocationTree node, Void unused) { MethodSymbol symbol = ASTHelpers.getSymbol(node); if (!symbol.isStatic()) { @@ -189,8 +188,7 @@ private static Matcher hasReceiverMatching(Matcher matcher) { return streamReceivers(tree).filter(t -> matcher.matches(t, state)).findFirst().orElse(null); } @@ -199,8 +197,7 @@ private static ExpressionTree findReceiverMatching( * Walks up the provided {@link ExpressionTree} to find the {@link IdentifierTree identifier} that * it stems from. Returns {@code null} if the tree doesn't terminate in an identifier. */ - @Nullable - private static IdentifierTree getRootIdentifier(ExpressionTree tree) { + private static @Nullable IdentifierTree getRootIdentifier(ExpressionTree tree) { if (tree == null) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/TypeCompatibility.java b/core/src/main/java/com/google/errorprone/bugpatterns/TypeCompatibility.java index bf827bd6e0d..cf522f39634 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/TypeCompatibility.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/TypeCompatibility.java @@ -43,9 +43,9 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** * Methods to answer the question: are these two types "compatible" with each other, in the context @@ -364,14 +364,11 @@ public abstract static class TypeCompatibilityReport { public abstract boolean isCompatible(); - @Nullable - public abstract Type lhs(); + public abstract @Nullable Type lhs(); - @Nullable - public abstract Type rhs(); + public abstract @Nullable Type rhs(); - @Nullable - public abstract String extraReason(); + public abstract @Nullable String extraReason(); static TypeCompatibilityReport compatible() { return COMPATIBLE; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryAnonymousClass.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryAnonymousClass.java index 4ec4f665604..3b7f0a25bd5 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryAnonymousClass.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryAnonymousClass.java @@ -55,9 +55,9 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import java.util.Objects; import java.util.Optional; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -233,8 +233,8 @@ public Void visitIdentifier(IdentifierTree node, Void unused) { /** * Replace the given {@code node} with the method reference specified by {@code this.newName}. */ - @Nullable - private SuggestedFix replaceUseWithMethodReference(ExpressionTree node, VisitorState state) { + private @Nullable SuggestedFix replaceUseWithMethodReference( + ExpressionTree node, VisitorState state) { Tree parent = state.getPath().getParentPath().getLeaf(); if (parent instanceof MemberSelectTree && ((MemberSelectTree) parent).getExpression().equals(node)) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryLambda.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryLambda.java index 46a81029ff0..dba027bd357 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryLambda.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryLambda.java @@ -68,7 +68,7 @@ import java.util.function.Predicate; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java index 0e8f41a500e..d01f7386a85 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java @@ -116,13 +116,13 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.type.NullType; import javax.tools.JavaFileObject; +import org.jspecify.annotations.Nullable; /** Bugpattern to detect unused declarations. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UseEnumSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/UseEnumSwitch.java index c52ecdb244c..29417dba95d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UseEnumSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UseEnumSwitch.java @@ -24,7 +24,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.tools.javac.code.Symbol; import javax.lang.model.element.ElementKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/android/FragmentInjection.java b/core/src/main/java/com/google/errorprone/bugpatterns/android/FragmentInjection.java index bb8a0fabea3..e4232014be6 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/android/FragmentInjection.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/android/FragmentInjection.java @@ -51,8 +51,8 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.FatalError; import com.sun.tools.javac.util.Name; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** * @author epmjohnston@google.com (Emily P.M. Johnston) @@ -121,8 +121,7 @@ public Description matchClass(ClassTree tree, VisitorState state) { * Return the first method tree on the given class tree that matches the given method matcher, * or null if one does not exist. */ - @Nullable - private static MethodTree getMethod( + private static @Nullable MethodTree getMethod( Matcher methodMatcher, ClassTree classTree, VisitorState state) { for (Tree member : classTree.getMembers()) { if (member instanceof MethodTree) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/android/IsLoggableTagLength.java b/core/src/main/java/com/google/errorprone/bugpatterns/android/IsLoggableTagLength.java index 2602353ea37..44d44585183 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/android/IsLoggableTagLength.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/android/IsLoggableTagLength.java @@ -46,7 +46,7 @@ import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.VarSymbol; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author epmjohnston@google.com (Emily P.M. Johnston) @@ -101,8 +101,7 @@ private static boolean isValidTag(String tag) { return Utf8.encodedLength(tag) <= 23; } - @Nullable - private static VariableTree findEnclosingIdentifier( + private static @Nullable VariableTree findEnclosingIdentifier( IdentifierTree originalNode, VisitorState state) { Symbol identifierSymbol = getSymbol(originalNode); if (!(identifierSymbol instanceof VarSymbol)) { @@ -112,9 +111,8 @@ private static VariableTree findEnclosingIdentifier( .findEnclosing(ClassTree.class) .accept( new TreeScanner() { - @Nullable @Override - public VariableTree visitVariable(VariableTree node, Void p) { + public @Nullable VariableTree visitVariable(VariableTree node, Void p) { return getSymbol(node).equals(identifierSymbol) ? node : null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/apidiff/ApiDiffChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/apidiff/ApiDiffChecker.java index 477fe922b50..f59064a7eb3 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/apidiff/ApiDiffChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/apidiff/ApiDiffChecker.java @@ -38,7 +38,7 @@ import com.sun.tools.javac.code.Types; import java.lang.annotation.Annotation; import java.util.Optional; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A base Error Prone check implementation to enforce compliance with a given API diff. */ public abstract class ApiDiffChecker extends BugChecker diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/EnclosedByReverseHeuristic.java b/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/EnclosedByReverseHeuristic.java index 54935fa59b6..29d37637af1 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/EnclosedByReverseHeuristic.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/EnclosedByReverseHeuristic.java @@ -24,7 +24,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.MethodSymbol; import java.util.Optional; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Detect whether the method invocation we are examining is enclosed by either a method or a class diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/LowInformationNameHeuristic.java b/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/LowInformationNameHeuristic.java index 1aa2adaedef..e0ba0c8f6cf 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/LowInformationNameHeuristic.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/argumentselectiondefects/LowInformationNameHeuristic.java @@ -20,7 +20,7 @@ import com.google.errorprone.VisitorState; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol.MethodSymbol; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A heuristic for checking if a formal parameter matches a predefined set of words which have been diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/AbstractCollectionIncompatibleTypeMatcher.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/AbstractCollectionIncompatibleTypeMatcher.java index 59184c9956f..637756e9d66 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/AbstractCollectionIncompatibleTypeMatcher.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/AbstractCollectionIncompatibleTypeMatcher.java @@ -30,7 +30,7 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Extracts the necessary information from a {@link MethodInvocationTree} to check whether calls to @@ -57,11 +57,9 @@ public abstract class AbstractCollectionIncompatibleTypeMatcher { * * @return the source type or null if not available */ - @Nullable - abstract Type extractSourceType(MethodInvocationTree tree, VisitorState state); + abstract @Nullable Type extractSourceType(MethodInvocationTree tree, VisitorState state); - @Nullable - abstract Type extractSourceType(MemberReferenceTree tree, VisitorState state); + abstract @Nullable Type extractSourceType(MemberReferenceTree tree, VisitorState state); /** * Returns the AST node from which the source type was extracted. Needed to produce readable error @@ -76,11 +74,10 @@ public abstract class AbstractCollectionIncompatibleTypeMatcher { * * @return the source AST node or null if not available */ - @Nullable - abstract ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state); + abstract @Nullable ExpressionTree extractSourceTree( + MethodInvocationTree tree, VisitorState state); - @Nullable - abstract ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state); + abstract @Nullable ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state); /** * Extracts the target type to which the source type must be castable. For example, in this code @@ -95,11 +92,9 @@ public abstract class AbstractCollectionIncompatibleTypeMatcher { * * @return the target type or null if not available */ - @Nullable - abstract Type extractTargetType(MethodInvocationTree tree, VisitorState state); + abstract @Nullable Type extractTargetType(MethodInvocationTree tree, VisitorState state); - @Nullable - abstract Type extractTargetType(MemberReferenceTree tree, VisitorState state); + abstract @Nullable Type extractTargetType(MemberReferenceTree tree, VisitorState state); /** * Encapsulates the result of matching a {@link Collection#contains}-like call, including the @@ -133,8 +128,7 @@ public Optional buildFix() { } } - @Nullable - public final MatchResult matches(ExpressionTree tree, VisitorState state) { + public final @Nullable MatchResult matches(ExpressionTree tree, VisitorState state) { if (!methodMatcher().matches(tree, state)) { return null; } @@ -160,8 +154,7 @@ public MatchResult visitMemberReference( }.visit(tree, null); } - @Nullable - private MatchResult getMatchResult( + private @Nullable MatchResult getMatchResult( @Nullable ExpressionTree sourceTree, @Nullable Type sourceType, @Nullable Type targetType) { if (sourceTree == null || sourceType == null || targetType == null) { return null; @@ -181,8 +174,7 @@ private MatchResult getMatchResult( * @param types the {@link Types} utility class from the {@link VisitorState} * @return the type argument, if defined, or null otherwise */ - @Nullable - protected static Type extractTypeArgAsMemberOfSupertype( + protected static @Nullable Type extractTypeArgAsMemberOfSupertype( Type type, Symbol superTypeSym, int typeArgIndex, Types types) { Type collectionType = types.asSuper(type, superTypeSym); if (collectionType == null) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/BinopMatcher.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/BinopMatcher.java index e93467b5cf9..cb1ee07496b 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/BinopMatcher.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/BinopMatcher.java @@ -25,7 +25,7 @@ import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Type; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; final class BinopMatcher extends AbstractCollectionIncompatibleTypeMatcher { @@ -42,9 +42,8 @@ Matcher methodMatcher() { return matcher; } - @Nullable @Override - Type extractSourceType(MethodInvocationTree tree, VisitorState state) { + @Nullable Type extractSourceType(MethodInvocationTree tree, VisitorState state) { return extractTypeArgAsMemberOfSupertype( getType(tree.getArguments().get(0)), state.getSymbolFromString(collectionType), @@ -52,9 +51,8 @@ Type extractSourceType(MethodInvocationTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - Type extractSourceType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractSourceType(MemberReferenceTree tree, VisitorState state) { Type descriptorType = state.getTypes().findDescriptorType(getType(tree)); return extractTypeArgAsMemberOfSupertype( descriptorType.getParameterTypes().get(0), @@ -63,21 +61,18 @@ Type extractSourceType(MemberReferenceTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state) { + @Nullable ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state) { return tree.getArguments().get(0); } - @Nullable @Override - ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { + @Nullable ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { return tree; } - @Nullable @Override - Type extractTargetType(MethodInvocationTree tree, VisitorState state) { + @Nullable Type extractTargetType(MethodInvocationTree tree, VisitorState state) { return extractTypeArgAsMemberOfSupertype( getType(tree.getArguments().get(1)), state.getSymbolFromString(collectionType), @@ -85,9 +80,8 @@ Type extractTargetType(MethodInvocationTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - Type extractTargetType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractTargetType(MemberReferenceTree tree, VisitorState state) { Type descriptorType = state.getTypes().findDescriptorType(getType(tree)); return extractTypeArgAsMemberOfSupertype( descriptorType.getParameterTypes().get(1), diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/CompatibleWithMisuse.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/CompatibleWithMisuse.java index e81e5eedd47..c72fb33eebe 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/CompatibleWithMisuse.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/CompatibleWithMisuse.java @@ -44,7 +44,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author glorioso@google.com (Nick Glorioso) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/ContainmentMatchers.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/ContainmentMatchers.java index ce6bd8e6d3c..53b837c59ff 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/ContainmentMatchers.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/ContainmentMatchers.java @@ -22,7 +22,7 @@ import com.google.errorprone.matchers.Matcher; import com.google.errorprone.matchers.Matchers; import com.sun.source.tree.ExpressionTree; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Matchers for methods which express containment, like {@link java.util.Collection#contains}. */ public final class ContainmentMatchers { @@ -106,8 +106,8 @@ public final class ContainmentMatchers { .addAll(STATIC_MATCHERS) .build(); - @Nullable - public static MatchResult firstNonNullMatchResult(ExpressionTree tree, VisitorState state) { + public static @Nullable MatchResult firstNonNullMatchResult( + ExpressionTree tree, VisitorState state) { if (!FIRST_ORDER_MATCHER.matches(tree, state)) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/IncompatibleArgumentType.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/IncompatibleArgumentType.java index 2390babf1a0..d2699c8d354 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/IncompatibleArgumentType.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/IncompatibleArgumentType.java @@ -44,10 +44,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.lang.model.element.Parameterizable; import javax.lang.model.element.TypeParameterElement; +import org.jspecify.annotations.Nullable; /** * @author glorioso@google.com (Nick Glorioso) @@ -68,8 +68,7 @@ public class IncompatibleArgumentType extends BugChecker implements MethodInvoca // null requiredType: I found the type variable, but I can't bind it to any type @AutoValue abstract static class RequiredType { - @Nullable - abstract Type type(); + abstract @Nullable Type type(); static RequiredType create(Type type) { return new AutoValue_IncompatibleArgumentType_RequiredType(type); @@ -206,10 +205,9 @@ private static boolean populateTypesToEnforce( return foundAnyTypeToEnforce; } - @Nullable - @CheckReturnValue // From calledReceiverType - private static RequiredType resolveRequiredTypeForThisCall( + @CheckReturnValue + private static @Nullable RequiredType resolveRequiredTypeForThisCall( VisitorState state, Type calledMethodType, Type calledReceiverType, @@ -226,8 +224,7 @@ private static RequiredType resolveRequiredTypeForThisCall( return requiredType; } - @Nullable - private static RequiredType resolveTypeFromGenericMethod( + private static @Nullable RequiredType resolveTypeFromGenericMethod( Type calledMethodType, MethodSymbol declaredMethod, String typeArgName) { int tyargIndex = findTypeArgInList(declaredMethod, typeArgName); return tyargIndex == -1 @@ -238,8 +235,7 @@ private static RequiredType resolveTypeFromGenericMethod( // Plumb through a type which is supposed to be a Types.Subst, then find the replacement // type that the compiler resolved. - @Nullable - private static Type getTypeFromTypeMapping( + private static @Nullable Type getTypeFromTypeMapping( Type m, MethodSymbol declaredMethod, String namedTypeArg) { ImmutableListMultimap substitutions = ASTHelpers.getTypeSubstitution(m, declaredMethod); @@ -254,8 +250,7 @@ private static Type getTypeFromTypeMapping( // class Foo { void something(@CW("X") Object x); } // new Foo().something(123); - @Nullable - private static RequiredType resolveTypeFromClass( + private static @Nullable RequiredType resolveTypeFromClass( Type calledType, ClassSymbol clazzSymbol, String typeArgName, VisitorState state) { // Try on the class int tyargIndex = findTypeArgInList(clazzSymbol, typeArgName); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/MethodArgMatcher.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/MethodArgMatcher.java index bac06dadb3b..5f5df71b7ca 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/MethodArgMatcher.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/MethodArgMatcher.java @@ -31,7 +31,7 @@ import com.sun.tools.javac.code.Type; import java.util.Collection; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Matches an instance method like {@link Collection#contains}, for which we just need to compare @@ -67,9 +67,8 @@ ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state) return Iterables.get(tree.getArguments(), methodArgIndex); } - @Nullable @Override - ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { + @Nullable ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { return tree; } @@ -78,9 +77,8 @@ Type extractSourceType(MethodInvocationTree tree, VisitorState state) { return getType(extractSourceTree(tree, state)); } - @Nullable @Override - Type extractSourceType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractSourceType(MemberReferenceTree tree, VisitorState state) { return state.getTypes().findDescriptorType(getType(tree)).getParameterTypes().get(0); } @@ -93,9 +91,8 @@ Type extractTargetType(MethodInvocationTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - Type extractTargetType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractTargetType(MemberReferenceTree tree, VisitorState state) { return extractTypeArgAsMemberOfSupertype( ASTHelpers.getReceiverType(tree), state.getSymbolFromString(typeName), diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/TypeArgOfMethodArgMatcher.java b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/TypeArgOfMethodArgMatcher.java index 1092422294a..e78e11dbaf8 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/TypeArgOfMethodArgMatcher.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/collectionincompatibletype/TypeArgOfMethodArgMatcher.java @@ -33,7 +33,7 @@ import com.sun.tools.javac.code.Type; import java.util.Collection; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Matches an instance method like {@link Collection#removeAll}, for which we need to extract the @@ -84,9 +84,8 @@ ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state) return Iterables.get(tree.getArguments(), methodArgIndex); } - @Nullable @Override - ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { + @Nullable ExpressionTree extractSourceTree(MemberReferenceTree tree, VisitorState state) { return tree; } @@ -99,9 +98,8 @@ Type extractSourceType(MethodInvocationTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - Type extractSourceType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractSourceType(MemberReferenceTree tree, VisitorState state) { return extractTypeArgAsMemberOfSupertype( getType(tree).allparams().get(methodArgIndex), state.getSymbolFromString(methodArgTypeName), @@ -118,9 +116,8 @@ Type extractTargetType(MethodInvocationTree tree, VisitorState state) { state.getTypes()); } - @Nullable @Override - Type extractTargetType(MemberReferenceTree tree, VisitorState state) { + @Nullable Type extractTargetType(MemberReferenceTree tree, VisitorState state) { return extractTypeArgAsMemberOfSupertype( ASTHelpers.getReceiverType(tree), state.getSymbolFromString(receiverTypeName), diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerArgumentToString.java b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerArgumentToString.java index 08e0a325c0e..62c00eb1b46 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerArgumentToString.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerArgumentToString.java @@ -49,8 +49,8 @@ import java.util.List; import java.util.regex.Pattern; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -106,7 +106,7 @@ static class Parameter { final Supplier source; final Type type; - @Nullable final Character placeholder; + final @Nullable Character placeholder; private Parameter(ExpressionTree expression, char placeholder) { this(s -> s.getSourceForNode(expression), getType(expression), placeholder); @@ -355,8 +355,8 @@ Description unwrapArguments( return describeMatch(tree, fix.build()); } - @Nullable - private static Parameter unwrap(ExpressionTree argument, char placeholder, VisitorState state) { + private static @Nullable Parameter unwrap( + ExpressionTree argument, char placeholder, VisitorState state) { for (Unwrapper unwrapper : Unwrapper.values()) { if (unwrapper.matcher.matches(argument, state)) { return unwrapper.unwrap((MethodInvocationTree) argument, placeholder); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerFormatString.java b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerFormatString.java index 2319851d42d..0368ea82cb6 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerFormatString.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerFormatString.java @@ -37,7 +37,7 @@ import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Symbol.MethodSymbol; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author cushon@google.com (Liam Miller-Cushon) @@ -83,8 +83,8 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState * If there were more arguments than format specifiers and the last argument is an exception, * suggest using {@code withCause(e)} instead of adding a format specifier. */ - @Nullable - private Fix withCauseFix(ValidationResult result, MethodInvocationTree tree, VisitorState state) { + private @Nullable Fix withCauseFix( + ValidationResult result, MethodInvocationTree tree, VisitorState state) { if (!result.message().startsWith("extra format arguments")) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerRedundantIsEnabled.java b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerRedundantIsEnabled.java index 11e1cacaa3c..03cadac121f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerRedundantIsEnabled.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerRedundantIsEnabled.java @@ -47,7 +47,7 @@ import com.sun.tools.javac.code.Symbol; import java.util.List; import java.util.Optional; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author mariasam@google.com (Maria Sam) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerWithoutCause.java b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerWithoutCause.java index 07f99a4b7a5..d5649146f45 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerWithoutCause.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/flogger/FloggerWithoutCause.java @@ -32,8 +32,8 @@ import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Type; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** * Detects Flogger log statements that pass Exceptions to the log method instead of using withCause. @@ -84,8 +84,7 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { String.format(".withCause(%s)", state.getSourceForNode(exception)))); } - @Nullable - private static Tree getExceptionArg(MethodInvocationTree tree, VisitorState state) { + private static @Nullable Tree getExceptionArg(MethodInvocationTree tree, VisitorState state) { for (Tree arg : Lists.reverse(tree.getArguments())) { try { Type argType = ASTHelpers.getType(arg); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatStringValidation.java b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatStringValidation.java index 732be68b862..b4c855f70ab 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatStringValidation.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/FormatStringValidation.java @@ -60,8 +60,8 @@ import java.util.UnknownFormatFlagsException; import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; /** Utilities for validating format strings. */ public final class FormatStringValidation { @@ -96,8 +96,7 @@ protected Void defaultAction(Tree tree, Void unused) { return flat.stream().map(t -> ASTHelpers.constValue(t, String.class)).filter(x -> x != null); } - @Nullable - public static ValidationResult validate( + public static @Nullable ValidationResult validate( @Nullable MethodSymbol formatMethodSymbol, Collection arguments, VisitorState state) { @@ -149,8 +148,7 @@ public static ValidationResult validate( * For example, an intance of {@link Integer} will be returned for an input of type {@code int} or * {@link Integer}. */ - @Nullable - private static Object getInstance(Tree tree, VisitorState state) { + private static @Nullable Object getInstance(Tree tree, VisitorState state) { Object value = ASTHelpers.constValue(tree); if (value != null) { return value; @@ -159,8 +157,7 @@ private static Object getInstance(Tree tree, VisitorState state) { return getInstance(type, state); } - @Nullable - private static Object getInstance(Type type, VisitorState state) { + private static @Nullable Object getInstance(Type type, VisitorState state) { Types types = state.getTypes(); if (type.getKind() == TypeKind.NULL) { return null; @@ -282,8 +279,7 @@ private static ValidationResult validate(String formatString, Object[] arguments return extraFormatArguments(formatString, asList(arguments)); } - @Nullable - private static ValidationResult extraFormatArguments( + private static @Nullable ValidationResult extraFormatArguments( String formatString, List arguments) { int used = IntStream.rangeClosed(0, arguments.size()) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/InlineFormatString.java b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/InlineFormatString.java index 4df787d5fdc..c55baa519b6 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/InlineFormatString.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/InlineFormatString.java @@ -54,7 +54,7 @@ import java.util.List; import java.util.Map; import javax.lang.model.element.ElementKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/StrictFormatStringValidation.java b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/StrictFormatStringValidation.java index b0c3f6894d5..692d3aa0c1f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/StrictFormatStringValidation.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/formatstring/StrictFormatStringValidation.java @@ -38,8 +38,8 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import java.util.List; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * Format string validation utility that fails on more cases than {@link FormatStringValidation} to @@ -50,8 +50,7 @@ public class StrictFormatStringValidation { private static final Matcher MOCKITO_MATCHERS = staticMethod().onClassAny("org.mockito.Matchers", "org.mockito.ArgumentMatchers"); - @Nullable - public static ValidationResult validate( + public static @Nullable ValidationResult validate( ExpressionTree formatStringTree, List args, VisitorState state) { if (MOCKITO_MATCHERS.matches(formatStringTree, state)) { // Mockito matchers do not pass standard @FormatString requirements, but we allow them so @@ -99,8 +98,7 @@ public static ValidationResult validate( } /** Helps {@code validate()} validate a format string that is declared as a method parameter. */ - @Nullable - private static ValidationResult validateFormatStringParameter( + private static @Nullable ValidationResult validateFormatStringParameter( ExpressionTree formatStringTree, Symbol formatStringSymbol, List args, @@ -214,9 +212,9 @@ public ValidationResult visitVariable(VariableTree node, Void unused) { return super.visitVariable(node, null); } - @Nullable @Override - public ValidationResult reduce(ValidationResult r1, ValidationResult r2) { + public @Nullable ValidationResult reduce( + ValidationResult r1, ValidationResult r2) { if (r1 == null && r2 == null) { return null; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/inject/MissingRuntimeRetention.java b/core/src/main/java/com/google/errorprone/bugpatterns/inject/MissingRuntimeRetention.java index 924f0bb9176..cac01623833 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/inject/MissingRuntimeRetention.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/inject/MissingRuntimeRetention.java @@ -51,7 +51,7 @@ import java.util.Collections; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author sgoldfeder@google.com (Steven Goldfeder) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/inject/dagger/AndroidInjectionBeforeSuper.java b/core/src/main/java/com/google/errorprone/bugpatterns/inject/dagger/AndroidInjectionBeforeSuper.java index a378f57b23c..7250abd7f50 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/inject/dagger/AndroidInjectionBeforeSuper.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/inject/dagger/AndroidInjectionBeforeSuper.java @@ -42,7 +42,7 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.SimpleTreeVisitor; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Ron Shapiro diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/inlineme/InlinabilityResult.java b/core/src/main/java/com/google/errorprone/bugpatterns/inlineme/InlinabilityResult.java index d511bf72863..89350fa6672 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/inlineme/InlinabilityResult.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/inlineme/InlinabilityResult.java @@ -51,7 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.lang.model.element.Modifier; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Whether an API can have {@code @InlineMe} applied to it or not. */ @AutoValue diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/EscapedEntity.java b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/EscapedEntity.java index 91e0f73a345..b5e0760f561 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/EscapedEntity.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/EscapedEntity.java @@ -36,7 +36,7 @@ import com.sun.source.util.DocTreePathScanner; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Finds unescaped entities in Javadocs. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/MissingSummary.java b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/MissingSummary.java index 4be9493fea7..d9c07e3c272 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/MissingSummary.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/MissingSummary.java @@ -49,8 +49,8 @@ import com.sun.tools.javac.tree.DCTree.DCDocComment; import java.util.List; import java.util.Set; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** * Matches Javadocs which are missing a required summary line. @@ -171,8 +171,7 @@ private Description generateSeeFix(DocTreePath docTreePath, SeeTree seeTree, Vis .build(); } - @Nullable - private static T findFirst(DocTreePath docTreePath, Class clazz) { + private static @Nullable T findFirst(DocTreePath docTreePath, Class clazz) { return new DocTreeScanner() { @Override public T scan(DocTree docTree, Void unused) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnescapedEntity.java b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnescapedEntity.java index f91d4226118..98a3595466e 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnescapedEntity.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnescapedEntity.java @@ -56,7 +56,7 @@ import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Finds unescaped entities in Javadocs. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnrecognisedJavadocTag.java b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnrecognisedJavadocTag.java index 9867cd4a11a..16a1fe46cdf 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnrecognisedJavadocTag.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/UnrecognisedJavadocTag.java @@ -42,7 +42,7 @@ import com.sun.tools.javac.tree.DCTree.DCDocComment; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Flags tags which haven't been recognised by the Javadoc parser. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/Utils.java b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/Utils.java index d4d48255880..1a81cf64478 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/Utils.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/javadoc/Utils.java @@ -37,7 +37,7 @@ import com.sun.tools.javac.util.Position; import java.lang.reflect.Method; import java.util.Optional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Common utility methods for fixing Javadocs. */ final class Utils { @@ -120,8 +120,7 @@ static DiagnosticPosition getDiagnosticPosition(int startPosition, Tree tree) { return new FixedPosition(tree, startPosition); } - @Nullable - static DocTreePath getDocTreePath(VisitorState state) { + static @Nullable DocTreePath getDocTreePath(VisitorState state) { DocCommentTree docCommentTree = getDocCommentTree(state); if (docCommentTree == null) { return null; @@ -129,8 +128,7 @@ static DocTreePath getDocTreePath(VisitorState state) { return new DocTreePath(state.getPath(), docCommentTree); } - @Nullable - private static DocCommentTree getDocCommentTree(VisitorState state) { + private static @Nullable DocCommentTree getDocCommentTree(VisitorState state) { return JavacTrees.instance(state.context).getDocCommentTree(state.getPath()); } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/nullness/EqualsBrokenForNull.java b/core/src/main/java/com/google/errorprone/bugpatterns/nullness/EqualsBrokenForNull.java index 0f00940fa6d..72d948df121 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/nullness/EqualsBrokenForNull.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/nullness/EqualsBrokenForNull.java @@ -50,7 +50,7 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import java.util.HashSet; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link BugChecker} adds a null check to {@code equals()} method implementations which don't @@ -131,8 +131,7 @@ public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { * Unwraps expressions like `(Foo) foo` or `((Foo) foo)` to return the VarSymbol of `foo`, or * null if the expression wasn't of this form. */ - @Nullable - private VarSymbol findVariable(Tree tree) { + private @Nullable VarSymbol findVariable(Tree tree) { while (tree != null) { switch (tree.getKind()) { case TYPE_CAST: diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/nullness/NullnessUtils.java b/core/src/main/java/com/google/errorprone/bugpatterns/nullness/NullnessUtils.java index 8d4e5c6dafc..32e5e63663d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/nullness/NullnessUtils.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/nullness/NullnessUtils.java @@ -83,8 +83,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import javax.annotation.Nullable; import javax.lang.model.element.Name; +import org.jspecify.annotations.Nullable; /** * Static utility methods for common functionality in the nullable checkers. @@ -305,8 +305,7 @@ final SuggestedFix fixPrefixingOnto( return prepareBuilder(state, suppressionToRemove).prefixWith(tree, "@" + use() + " ").build(); } - @Nullable - abstract String importToAdd(); + abstract @Nullable String importToAdd(); abstract String use(); @@ -390,8 +389,7 @@ private static boolean isTypeUse(String className) { } } - @Nullable - static NullCheck getNullCheck(ExpressionTree tree) { + static @Nullable NullCheck getNullCheck(ExpressionTree tree) { tree = stripParentheses(tree); Polarity polarity; @@ -456,12 +454,10 @@ abstract static class NullCheck { * that form. Prefer this over {@link #varSymbolButUsuallyPreferBareIdentifier} in most cases, * as discussed in the class documentation. */ - @Nullable - abstract Name bareIdentifier(); + abstract @Nullable Name bareIdentifier(); /** Returns the symbol that was checked against {@code null}. */ - @Nullable - abstract VarSymbol varSymbolButUsuallyPreferBareIdentifier(); + abstract @Nullable VarSymbol varSymbolButUsuallyPreferBareIdentifier(); abstract Polarity polarity(); @@ -669,8 +665,7 @@ public static ImmutableSet varsProvenNullByParentTernary(TreePath path) { return ImmutableSet.of(); } - @Nullable - static VariableTree findDeclaration(VisitorState state, Symbol sym) { + static @Nullable VariableTree findDeclaration(VisitorState state, Symbol sym) { JavacProcessingEnvironment javacEnv = JavacProcessingEnvironment.instance(state.context); TreePath declPath = Trees.instance(javacEnv).getPath(sym); // Skip fields declared in other compilation units since we can't make a fix for them here. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ConstantExpressions.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ConstantExpressions.java index d265bde5b0e..b1206cd7eb2 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ConstantExpressions.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ConstantExpressions.java @@ -67,7 +67,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import javax.inject.Inject; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Helper for establishing whether expressions correspond to a constant expression. */ public final class ConstantExpressions { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/DoubleCheckedLocking.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/DoubleCheckedLocking.java index 1561241f2a7..e82d55fb9b9 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/DoubleCheckedLocking.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/DoubleCheckedLocking.java @@ -50,9 +50,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ // TODO(cushon): allow @LazyInit on fields as a suppression mechanism? @@ -218,8 +218,7 @@ static DCLInfo create( * Gaps before the synchronized or inner 'if' statement are ignored, and the operands in the * null-checks are accepted in either order. */ - @Nullable - private static DCLInfo findDcl(IfTree outerIf) { + private static @Nullable DCLInfo findDcl(IfTree outerIf) { // TODO(cushon): Optional.ifPresent... ExpressionTree outerIfTest = getNullCheckedExpression(outerIf.getCondition()); if (outerIfTest == null) { @@ -251,8 +250,7 @@ private static DCLInfo findDcl(IfTree outerIf) { /** * Matches comparisons to null (e.g. {@code foo == null}) and returns the expression being tested. */ - @Nullable - private static ExpressionTree getNullCheckedExpression(ExpressionTree condition) { + private static @Nullable ExpressionTree getNullCheckedExpression(ExpressionTree condition) { condition = stripParentheses(condition); if (!(condition instanceof BinaryTree)) { return null; @@ -309,8 +307,7 @@ private T visit(List tx) { * Since double-checked locking should always be used on a private field, this should be * reasonably effective. */ - @Nullable - private static JCTree findFieldDeclaration(TreePath path, VarSymbol var) { + private static @Nullable JCTree findFieldDeclaration(TreePath path, VarSymbol var) { for (TreePath curr = path; curr != null; curr = curr.getParentPath()) { Tree leaf = curr.getLeaf(); if (!(leaf instanceof JCClassDecl)) { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinder.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinder.java index 5a6fa10d178..000442e1922 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinder.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByBinder.java @@ -37,7 +37,7 @@ import com.sun.tools.javac.util.Names; import java.util.Optional; import javax.lang.model.element.Name; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A binder from {@code @GuardedBy} annotations to {@link GuardedByExpression}s. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByChecker.java index bfe118b3249..03914f9d562 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByChecker.java @@ -44,7 +44,7 @@ import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedBySymbolResolver.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedBySymbolResolver.java index 0a63bd2cc99..0a7b5e9afc8 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedBySymbolResolver.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedBySymbolResolver.java @@ -43,9 +43,9 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; +import org.jspecify.annotations.Nullable; /** * A symbol resolver used while binding guardedby expressions from string literals. @@ -211,8 +211,7 @@ private T getMember( return null; } - @Nullable - private VarSymbol getParam(@Nullable MethodInfo method, String name) { + private @Nullable VarSymbol getParam(@Nullable MethodInfo method, String name) { if (method == null) { return null; } @@ -234,9 +233,8 @@ private VarSymbol getParam(@Nullable MethodInfo method, String name) { return null; } - @Nullable @Override - public Symbol resolveTypeLiteral(ExpressionTree expr) { + public @Nullable Symbol resolveTypeLiteral(ExpressionTree expr) { checkGuardedBy(expr instanceof IdentifierTree, "bad type literal: %s", expr); IdentifierTree ident = (IdentifierTree) expr; Symbol type = resolveType(ident.getName().toString(), SearchSuperTypes.YES); @@ -276,8 +274,7 @@ private Symbol resolveType(String name, SearchSuperTypes searchSuperTypes) { return type; } - @Nullable - private Symbol getSuperType(Symbol symbol, String name) { + private @Nullable Symbol getSuperType(Symbol symbol, String name) { for (Type t : types.closure(symbol.type)) { if (t.asElement().getSimpleName().contentEquals(name)) { return t.asElement(); @@ -286,8 +283,7 @@ private Symbol getSuperType(Symbol symbol, String name) { return null; } - @Nullable - private static Symbol getLexicallyEnclosing(ClassSymbol symbol, String name) { + private static @Nullable Symbol getLexicallyEnclosing(ClassSymbol symbol, String name) { Symbol current = symbol.owner; while (true) { if (current == null || current.getSimpleName().contentEquals(name)) { @@ -308,9 +304,8 @@ private Symbol attribIdent(String name) { return attr.attribIdent(tm.Ident(visitorState.getName(name)), compilationUnit); } - @Nullable @Override - public Symbol resolveEnclosingClass(ExpressionTree expr) { + public @Nullable Symbol resolveEnclosingClass(ExpressionTree expr) { checkGuardedBy(expr instanceof IdentifierTree, "bad type literal: %s", expr); IdentifierTree ident = (IdentifierTree) expr; Symbol type = resolveType(ident.getName().toString(), SearchSuperTypes.NO); @@ -330,11 +325,9 @@ abstract static class MethodInfo { * The method arguments, if the site is a method invocation expression for a method annotated * with {@code @GuardedBy}. */ - @Nullable - abstract ImmutableList arguments(); + abstract @Nullable ImmutableList arguments(); - @Nullable - ExpressionTree argument(int idx) { + @Nullable ExpressionTree argument(int idx) { if (arguments() == null) { return null; } @@ -363,8 +356,7 @@ static MethodInfo create(MethodSymbol sym, ImmutableList argumen return new AutoValue_GuardedBySymbolResolver_MethodInfo(sym, arguments); } - @Nullable - static MethodInfo create(Tree tree, VisitorState visitorState) { + static @Nullable MethodInfo create(Tree tree, VisitorState visitorState) { Symbol sym = ASTHelpers.getSymbol(tree); if (!(sym instanceof MethodSymbol)) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByUtils.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByUtils.java index 87549bb6270..ee472cb6110 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByUtils.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/GuardedByUtils.java @@ -37,7 +37,7 @@ import java.util.Optional; import java.util.stream.Stream; import javax.lang.model.element.ElementKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * @author cushon@google.com (Liam Miller-Cushon) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableAnalysis.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableAnalysis.java index d147599d9cd..fbfb7c3813e 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableAnalysis.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableAnalysis.java @@ -48,7 +48,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** Analyzes types for deep immutability. */ public final class ImmutableAnalysis { diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableChecker.java index b9c744e5d28..f6220b83d90 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableChecker.java @@ -83,7 +83,7 @@ import java.util.Set; import javax.inject.Inject; import javax.lang.model.element.ElementKind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableRefactoring.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableRefactoring.java index 0b5b1badcaa..18493c7f13e 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableRefactoring.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ImmutableRefactoring.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableSet; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; +import com.google.errorprone.annotations.Immutable; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher; import com.google.errorprone.fixes.SuggestedFix; @@ -51,22 +52,19 @@ public class ImmutableRefactoring extends BugChecker implements CompilationUnitT this.wellKnownMutability = wellKnownMutability; } + private static final String JSR_305_IMMUTABLE = "javax.annotation.concurrent.Immutable"; + @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { ImmutableChecker immutableChecker = new ImmutableChecker( - wellKnownMutability, - ImmutableSet.of( - javax.annotation.concurrent.Immutable.class.getName(), - com.google.errorprone.annotations.Immutable.class.getName())); + wellKnownMutability, ImmutableSet.of(JSR_305_IMMUTABLE, Immutable.class.getName())); Optional immutableImport = tree.getImports().stream() .filter( i -> { Symbol s = ASTHelpers.getSymbol(i.getQualifiedIdentifier()); - return s != null - && s.getQualifiedName() - .contentEquals(javax.annotation.concurrent.Immutable.class.getName()); + return s != null && s.getQualifiedName().contentEquals(JSR_305_IMMUTABLE); }) .findFirst(); if (!immutableImport.isPresent()) { @@ -76,8 +74,7 @@ public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState s new TreePathScanner() { @Override public Void visitClass(ClassTree node, Void unused) { - if (!ASTHelpers.hasAnnotation( - node, javax.annotation.concurrent.Immutable.class.getName(), state)) { + if (!ASTHelpers.hasAnnotation(node, JSR_305_IMMUTABLE, state)) { return super.visitClass(node, null); } boolean violator = @@ -98,9 +95,7 @@ public Void visitClass(ClassTree node, Void unused) { }.scan(state.getPath(), null); SuggestedFix.Builder fixBuilder = - SuggestedFix.builder() - .removeImport(javax.annotation.concurrent.Immutable.class.getName()) - .addImport(com.google.errorprone.annotations.Immutable.class.getName()); + SuggestedFix.builder().removeImport(JSR_305_IMMUTABLE).addImport(Immutable.class.getName()); for (ClassTree classTree : notOk) { getAnnotationsWithSimpleName(classTree.getModifiers().getAnnotations(), "Immutable") .forEach(fixBuilder::delete); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeChecker.java index b2bd5a0314b..8823038dc2d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafeChecker.java @@ -56,7 +56,7 @@ import java.util.Map.Entry; import java.util.Optional; import javax.inject.Inject; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java index 693d756acca..8e1ba318bd5 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/threadsafety/ThreadSafety.java @@ -65,9 +65,9 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; import javax.lang.model.type.TypeKind; +import org.jspecify.annotations.Nullable; import org.pcollections.ConsPStack; /** @@ -747,8 +747,7 @@ public AnnotationInfo getMarkerOrAcceptedAnnotation(Symbol sym, VisitorState sta } /** Returns an enclosing instance for the specified type if it is thread-safe. */ - @Nullable - public Type mutableEnclosingInstance(Optional tree, ClassType type) { + public @Nullable Type mutableEnclosingInstance(Optional tree, ClassType type) { if (tree.isPresent() && !CanBeStaticAnalyzer.referencesOuter( tree.get(), ASTHelpers.getSymbol(tree.get()), state)) { @@ -813,8 +812,7 @@ public Set threadSafeTypeParametersInScope(Symbol sym) { return result.build(); } - @Nullable - private AnnotationInfo getAnnotation( + private @Nullable AnnotationInfo getAnnotation( Symbol sym, ImmutableSet annotationsToCheck, VisitorState state) { if (sym == null) { return null; diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/time/TimeUnitMismatch.java b/core/src/main/java/com/google/errorprone/bugpatterns/time/TimeUnitMismatch.java index 38329e38085..9ed7fa74608 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/time/TimeUnitMismatch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/time/TimeUnitMismatch.java @@ -72,7 +72,7 @@ import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Checker that detects likely time-unit mismatches by looking at identifier names. */ @BugPattern( @@ -317,8 +317,7 @@ private boolean check(String formalName, ExpressionTree actualTree, VisitorState *

    "Nomen est Omen: Exploring and Exploiting Similarities between Argument and Parameter * Names," ICSE 2016 */ - @Nullable - private static String extractArgumentName(ExpressionTree expr) { + private static @Nullable String extractArgumentName(ExpressionTree expr) { switch (expr.getKind()) { case TYPE_CAST: return extractArgumentName(((TypeCastTree) expr).getExpression()); @@ -371,9 +370,8 @@ private static String extractArgumentName(ExpressionTree expr) { isSameType("java.lang.Long"), isSameType("java.lang.Double")); - @Nullable @VisibleForTesting - static TimeUnit unitSuggestedByName(String name) { + static @Nullable TimeUnit unitSuggestedByName(String name) { // Tuple types, especially Pair, trip us up. Skip APIs that might be from them. // This check is somewhat redundant with the "second" check below. // TODO(cpovirk): Skip APIs only if they're from a type that also declares a first/getFirst()? diff --git a/core/src/main/java/com/google/errorprone/refaster/Bindings.java b/core/src/main/java/com/google/errorprone/refaster/Bindings.java index 1b4754dfdb8..1d99777ce2b 100644 --- a/core/src/main/java/com/google/errorprone/refaster/Bindings.java +++ b/core/src/main/java/com/google/errorprone/refaster/Bindings.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A type-safe map from objects of type {@code Bindings.Key}, which consist of a {@code String} @@ -130,9 +130,8 @@ public V putBinding(Key key, V value) { } @CanIgnoreReturnValue - @Nullable @Override - public Object put(Key key, Object value) { + public @Nullable Object put(Key key, Object value) { checkNotNull(key, "key"); checkNotNull(value, "value"); return super.put(key, key.getValueType().getRawType().cast(value)); diff --git a/core/src/main/java/com/google/errorprone/refaster/ExpressionTemplate.java b/core/src/main/java/com/google/errorprone/refaster/ExpressionTemplate.java index 2bbc310edff..dde63b964e8 100644 --- a/core/src/main/java/com/google/errorprone/refaster/ExpressionTemplate.java +++ b/core/src/main/java/com/google/errorprone/refaster/ExpressionTemplate.java @@ -58,7 +58,7 @@ import java.lang.annotation.Annotation; import java.util.Map; import java.util.logging.Logger; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Implementation of a template to match and replace an expression anywhere in an AST. diff --git a/core/src/main/java/com/google/errorprone/refaster/PlaceholderUnificationVisitor.java b/core/src/main/java/com/google/errorprone/refaster/PlaceholderUnificationVisitor.java index dea7fcc574f..faee2aa028f 100644 --- a/core/src/main/java/com/google/errorprone/refaster/PlaceholderUnificationVisitor.java +++ b/core/src/main/java/com/google/errorprone/refaster/PlaceholderUnificationVisitor.java @@ -107,7 +107,7 @@ import java.util.EnumSet; import java.util.Map; import java.util.function.BiFunction; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Given a tree as input, returns all the ways this placeholder invocation could be matched with @@ -136,8 +136,7 @@ static State create( public abstract Unifier unifier(); - @Nullable - public abstract R result(); + public abstract @Nullable R result(); public State withResult(R2 result) { return create(seenParameters(), unifier(), result); diff --git a/core/src/main/java/com/google/errorprone/refaster/StringName.java b/core/src/main/java/com/google/errorprone/refaster/StringName.java index 50ec4d899d7..fa42035e527 100644 --- a/core/src/main/java/com/google/errorprone/refaster/StringName.java +++ b/core/src/main/java/com/google/errorprone/refaster/StringName.java @@ -17,8 +17,8 @@ package com.google.errorprone.refaster; import com.google.auto.value.AutoValue; -import javax.annotation.Nullable; import javax.lang.model.element.Name; +import org.jspecify.annotations.Nullable; /** * A simple wrapper to view a {@code String} as a {@code Name}. diff --git a/core/src/main/java/com/google/errorprone/refaster/Template.java b/core/src/main/java/com/google/errorprone/refaster/Template.java index d48cf85e10f..4c226065741 100644 --- a/core/src/main/java/com/google/errorprone/refaster/Template.java +++ b/core/src/main/java/com/google/errorprone/refaster/Template.java @@ -77,7 +77,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.logging.Logger; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Abstract superclass for templates that can be used to search and replace in a Java syntax tree. @@ -192,7 +192,6 @@ protected List actualTypes(Inliner inliner) throws CouldNotResolveImportEx return List.from(result); } - @Nullable protected Optional typecheck( Unifier unifier, Inliner inliner, @@ -490,8 +489,7 @@ private Type infer( } /** Reflectively instantiate the package-private {@code MethodResolutionPhase} enum. */ - @Nullable - private static Object newMethodResolutionPhase(boolean autoboxing) { + private static @Nullable Object newMethodResolutionPhase(boolean autoboxing) { for (Class c : Resolve.class.getDeclaredClasses()) { if (!c.getName().equals("com.sun.tools.javac.comp.Resolve$MethodResolutionPhase")) { continue; diff --git a/core/src/main/java/com/google/errorprone/refaster/UAnnotation.java b/core/src/main/java/com/google/errorprone/refaster/UAnnotation.java index 6f354982ed1..1d2d73dc206 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UAnnotation.java +++ b/core/src/main/java/com/google/errorprone/refaster/UAnnotation.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link AnnotationTree}. @@ -49,8 +49,7 @@ public static UAnnotation create(UTree annotationType, UExpression... argumen public abstract ImmutableList getArguments(); @Override - @Nullable - public Choice visitAnnotation(AnnotationTree annotation, Unifier unifier) { + public @Nullable Choice visitAnnotation(AnnotationTree annotation, Unifier unifier) { return getAnnotationType() .unify(annotation.getAnnotationType(), unifier) .thenChoose(unifications(getArguments(), annotation.getArguments())); diff --git a/core/src/main/java/com/google/errorprone/refaster/UArrayType.java b/core/src/main/java/com/google/errorprone/refaster/UArrayType.java index 1752f0e5143..f1ea8830a8c 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UArrayType.java +++ b/core/src/main/java/com/google/errorprone/refaster/UArrayType.java @@ -18,7 +18,7 @@ import com.google.auto.value.AutoValue; import com.sun.tools.javac.code.Type.ArrayType; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UType} version of {@link ArrayType}, which represents a type {@code T[]} based on the type @@ -35,8 +35,7 @@ public static UArrayType create(UType componentType) { abstract UType componentType(); @Override - @Nullable - public Choice visitArrayType(ArrayType arrayType, @Nullable Unifier unifier) { + public @Nullable Choice visitArrayType(ArrayType arrayType, @Nullable Unifier unifier) { return componentType().unify(arrayType.getComponentType(), unifier); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UArrayTypeTree.java b/core/src/main/java/com/google/errorprone/refaster/UArrayTypeTree.java index 4d9ba4afdf7..ee026378d9b 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UArrayTypeTree.java +++ b/core/src/main/java/com/google/errorprone/refaster/UArrayTypeTree.java @@ -20,7 +20,7 @@ import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link ArrayTypeTree}. This is the AST representation of {@link @@ -38,8 +38,7 @@ public static UArrayTypeTree create(UExpression elementType) { public abstract UExpression getType(); @Override - @Nullable - public Choice visitArrayType(ArrayTypeTree tree, @Nullable Unifier unifier) { + public @Nullable Choice visitArrayType(ArrayTypeTree tree, @Nullable Unifier unifier) { return getType().unify(tree.getType(), unifier); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UAssert.java b/core/src/main/java/com/google/errorprone/refaster/UAssert.java index 3901e451b51..e7395db598e 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UAssert.java +++ b/core/src/main/java/com/google/errorprone/refaster/UAssert.java @@ -22,7 +22,7 @@ import com.sun.source.tree.AssertTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCStatement; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of an assertion. @@ -50,8 +50,7 @@ public R accept(TreeVisitor visitor, D data) { public abstract UExpression getCondition(); @Override - @Nullable - public abstract UExpression getDetail(); + public abstract @Nullable UExpression getDetail(); @Override public Choice visitAssert(AssertTree node, Unifier unifier) { diff --git a/core/src/main/java/com/google/errorprone/refaster/UBreak.java b/core/src/main/java/com/google/errorprone/refaster/UBreak.java index 5aadd598140..74c86f1b0f3 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UBreak.java +++ b/core/src/main/java/com/google/errorprone/refaster/UBreak.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Name; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of {@code BreakTree}. @@ -45,14 +45,12 @@ static UBreak create(@Nullable CharSequence label) { } // TODO(b/176098078): Add @Override once compiling JDK 12+ - @Nullable - public ExpressionTree getValue() { + public @Nullable ExpressionTree getValue() { return null; } @Override - @Nullable - public StringName getLabel() { + public @Nullable StringName getLabel() { return label; } diff --git a/core/src/main/java/com/google/errorprone/refaster/UCatch.java b/core/src/main/java/com/google/errorprone/refaster/UCatch.java index eabab3abd08..3e400978a26 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UCatch.java +++ b/core/src/main/java/com/google/errorprone/refaster/UCatch.java @@ -22,7 +22,7 @@ import com.sun.source.tree.CatchTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCCatch; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code CatchTree}. @@ -57,8 +57,7 @@ public JCCatch inline(Inliner inliner) throws CouldNotResolveImportException { } @Override - @Nullable - public Choice visitCatch(CatchTree node, @Nullable Unifier unifier) { + public @Nullable Choice visitCatch(CatchTree node, @Nullable Unifier unifier) { return getParameter() .unify(node.getParameter(), unifier) .thenChoose(unifications(getBlock(), node.getBlock())); diff --git a/core/src/main/java/com/google/errorprone/refaster/UClassIdent.java b/core/src/main/java/com/google/errorprone/refaster/UClassIdent.java index 37a23b5462f..a41878c24ea 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UClassIdent.java +++ b/core/src/main/java/com/google/errorprone/refaster/UClassIdent.java @@ -27,7 +27,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Identifier for a class type. @@ -71,13 +71,11 @@ public JCExpression inline(Inliner inliner) throws CouldNotResolveImportExceptio } @Override - @Nullable - protected Choice defaultAction(Tree tree, @Nullable Unifier unifier) { + protected @Nullable Choice defaultAction(Tree tree, @Nullable Unifier unifier) { return unify(ASTHelpers.getSymbol(tree), unifier); } - @Nullable - public Choice unify(@Nullable Symbol symbol, Unifier unifier) { + public @Nullable Choice unify(@Nullable Symbol symbol, Unifier unifier) { return symbol != null ? getName().unify(symbol.getQualifiedName(), unifier) : Choice.none(); diff --git a/core/src/main/java/com/google/errorprone/refaster/UConditional.java b/core/src/main/java/com/google/errorprone/refaster/UConditional.java index 9d96ed7cab2..f1b19f9c6a7 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UConditional.java +++ b/core/src/main/java/com/google/errorprone/refaster/UConditional.java @@ -22,7 +22,7 @@ import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCConditional; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link ConditionalExpressionTree}. @@ -46,8 +46,7 @@ public static UConditional create( public abstract UExpression getFalseExpression(); @Override - @Nullable - public Choice visitConditionalExpression( + public @Nullable Choice visitConditionalExpression( ConditionalExpressionTree conditional, Unifier unifier) { return getCondition() .unify(conditional.getCondition(), unifier.fork()) diff --git a/core/src/main/java/com/google/errorprone/refaster/UContinue.java b/core/src/main/java/com/google/errorprone/refaster/UContinue.java index 590393e6260..0f32a9fb824 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UContinue.java +++ b/core/src/main/java/com/google/errorprone/refaster/UContinue.java @@ -20,7 +20,7 @@ import com.sun.source.tree.ContinueTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCContinue; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of {@code ContinueTree}. @@ -34,8 +34,7 @@ static UContinue create(@Nullable CharSequence label) { } @Override - @Nullable - public abstract StringName getLabel(); + public abstract @Nullable StringName getLabel(); @Override public Kind getKind() { diff --git a/core/src/main/java/com/google/errorprone/refaster/UDoWhileLoop.java b/core/src/main/java/com/google/errorprone/refaster/UDoWhileLoop.java index 13a36b2ac78..94fb63d4544 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UDoWhileLoop.java +++ b/core/src/main/java/com/google/errorprone/refaster/UDoWhileLoop.java @@ -22,7 +22,7 @@ import com.sun.source.tree.DoWhileLoopTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@link UTree} representation of a {@link DoWhileLoopTree}. @@ -42,8 +42,8 @@ public static UDoWhileLoop create(UStatement body, UExpression condition) { public abstract UExpression getCondition(); @Override - @Nullable - public Choice visitDoWhileLoop(DoWhileLoopTree loop, @Nullable Unifier unifier) { + public @Nullable Choice visitDoWhileLoop( + DoWhileLoopTree loop, @Nullable Unifier unifier) { return getStatement() .unify(loop.getStatement(), unifier) .thenChoose(unifications(getCondition(), loop.getCondition())); diff --git a/core/src/main/java/com/google/errorprone/refaster/UExpressionStatement.java b/core/src/main/java/com/google/errorprone/refaster/UExpressionStatement.java index 663ed3d0e1c..7fc1c05f867 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UExpressionStatement.java +++ b/core/src/main/java/com/google/errorprone/refaster/UExpressionStatement.java @@ -20,7 +20,7 @@ import com.sun.source.tree.ExpressionStatementTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of a {@link ExpressionStatementTree}. @@ -37,8 +37,7 @@ public static UExpressionStatement create(UExpression expression) { public abstract UExpression getExpression(); @Override - @Nullable - public Choice visitExpressionStatement( + public @Nullable Choice visitExpressionStatement( ExpressionStatementTree expressionStatement, @Nullable Unifier unifier) { return getExpression().unify(expressionStatement.getExpression(), unifier); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UForLoop.java b/core/src/main/java/com/google/errorprone/refaster/UForLoop.java index b7dea9197da..405d16ba3c0 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UForLoop.java +++ b/core/src/main/java/com/google/errorprone/refaster/UForLoop.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCForLoop; import com.sun.tools.javac.tree.JCTree.JCStatement; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of a {@link ForLoopTree}. @@ -51,8 +51,7 @@ public static UForLoop create( public abstract ImmutableList getInitializer(); @Override - @Nullable - public abstract UExpression getCondition(); + public abstract @Nullable UExpression getCondition(); @Override public abstract ImmutableList getUpdate(); @@ -61,8 +60,7 @@ public static UForLoop create( public abstract USimpleStatement getStatement(); @Override - @Nullable - public Choice visitForLoop(ForLoopTree loop, @Nullable Unifier unifier) { + public @Nullable Choice visitForLoop(ForLoopTree loop, @Nullable Unifier unifier) { return UBlock.unifyStatementList(getInitializer(), loop.getInitializer(), unifier) .thenChoose(unifications(getCondition(), loop.getCondition())) .thenChoose(unifications(getUpdate(), loop.getUpdate())) diff --git a/core/src/main/java/com/google/errorprone/refaster/UFreeIdent.java b/core/src/main/java/com/google/errorprone/refaster/UFreeIdent.java index ffe9391f4cf..41228b5bd87 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UFreeIdent.java +++ b/core/src/main/java/com/google/errorprone/refaster/UFreeIdent.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.util.Names; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Free identifier that can be bound to any expression of the appropriate type. diff --git a/core/src/main/java/com/google/errorprone/refaster/UIf.java b/core/src/main/java/com/google/errorprone/refaster/UIf.java index 1ecdee4d793..dd16bc6f895 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UIf.java +++ b/core/src/main/java/com/google/errorprone/refaster/UIf.java @@ -27,7 +27,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of an {@link IfTree}. @@ -48,8 +48,7 @@ public static UIf create( public abstract UStatement getThenStatement(); @Override - @Nullable - public abstract UStatement getElseStatement(); + public abstract @Nullable UStatement getElseStatement(); @Override public R accept(TreeVisitor visitor, D data) { @@ -76,8 +75,8 @@ private static Function> unifyUStatementWithSingleState } @Override - @Nullable - public Choice apply(UnifierWithUnconsumedStatements state) { + public @Nullable Choice apply( + UnifierWithUnconsumedStatements state) { ImmutableList unconsumedStatements = state.unconsumedStatements(); if (unconsumedStatements.isEmpty()) { return Choice.none(); diff --git a/core/src/main/java/com/google/errorprone/refaster/UInstanceOf.java b/core/src/main/java/com/google/errorprone/refaster/UInstanceOf.java index aa79cae227d..e3ffa9d41f6 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UInstanceOf.java +++ b/core/src/main/java/com/google/errorprone/refaster/UInstanceOf.java @@ -23,7 +23,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import java.lang.reflect.Proxy; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of a {@link InstanceOfTree}. @@ -78,8 +78,8 @@ public JCInstanceOf inline(Inliner inliner) throws CouldNotResolveImportExceptio } @Override - @Nullable - public Choice visitInstanceOf(InstanceOfTree instanceOf, @Nullable Unifier unifier) { + public @Nullable Choice visitInstanceOf( + InstanceOfTree instanceOf, @Nullable Unifier unifier) { return getExpression() .unify(instanceOf.getExpression(), unifier) .thenChoose(unifications(getType(), instanceOf.getType())); diff --git a/core/src/main/java/com/google/errorprone/refaster/ULabeledStatement.java b/core/src/main/java/com/google/errorprone/refaster/ULabeledStatement.java index 0f92544e76d..879b35d95ce 100644 --- a/core/src/main/java/com/google/errorprone/refaster/ULabeledStatement.java +++ b/core/src/main/java/com/google/errorprone/refaster/ULabeledStatement.java @@ -21,7 +21,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; import com.sun.tools.javac.util.Name; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code LabeledStatementTree}. @@ -44,8 +44,7 @@ static class Key extends Bindings.Key { * Returns either the {@code Name} bound to the specified label, or a {@code Name} representing * the original label if none is already bound. */ - @Nullable - static Name inlineLabel(@Nullable CharSequence label, Inliner inliner) { + static @Nullable Name inlineLabel(@Nullable CharSequence label, Inliner inliner) { return (label == null) ? null : inliner.asName(inliner.getOptionalBinding(new Key(label)).or(label)); diff --git a/core/src/main/java/com/google/errorprone/refaster/ULiteral.java b/core/src/main/java/com/google/errorprone/refaster/ULiteral.java index ca76c364489..b4e3919d448 100644 --- a/core/src/main/java/com/google/errorprone/refaster/ULiteral.java +++ b/core/src/main/java/com/google/errorprone/refaster/ULiteral.java @@ -26,7 +26,7 @@ import com.sun.tools.javac.code.TypeTag; import com.sun.tools.javac.tree.JCTree.JCLiteral; import java.util.Objects; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link LiteralTree}. @@ -89,8 +89,7 @@ public static ULiteral create(Kind kind, Object value) { public abstract Kind getKind(); @Override - @Nullable - public abstract Object getValue(); + public abstract @Nullable Object getValue(); private static boolean integral(@Nullable Object o) { return o instanceof Integer || o instanceof Long; diff --git a/core/src/main/java/com/google/errorprone/refaster/UMatches.java b/core/src/main/java/com/google/errorprone/refaster/UMatches.java index ac1cefcc279..f6c07a3a5d9 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UMatches.java +++ b/core/src/main/java/com/google/errorprone/refaster/UMatches.java @@ -27,7 +27,7 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.util.Context; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UMatches} allows conditionally matching a {@code UExpression} predicated by an error-prone @@ -52,8 +52,7 @@ public static UMatches create( abstract UExpression expression(); @Override - @Nullable - protected Choice defaultAction(Tree target, @Nullable Unifier unifier) { + protected @Nullable Choice defaultAction(Tree target, @Nullable Unifier unifier) { Tree exprTarget = ASTHelpers.stripParentheses(target); return expression() .unify(exprTarget, unifier) diff --git a/core/src/main/java/com/google/errorprone/refaster/UMemberReference.java b/core/src/main/java/com/google/errorprone/refaster/UMemberReference.java index 30bae9a6ed6..018ec360913 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UMemberReference.java +++ b/core/src/main/java/com/google/errorprone/refaster/UMemberReference.java @@ -23,7 +23,7 @@ import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCMemberReference; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code MemberReferenceTree} @@ -83,6 +83,5 @@ public JCMemberReference inline(Inliner inliner) throws CouldNotResolveImportExc public abstract StringName getName(); @Override - @Nullable - public abstract ImmutableList getTypeArguments(); + public abstract @Nullable ImmutableList getTypeArguments(); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UMethodDecl.java b/core/src/main/java/com/google/errorprone/refaster/UMethodDecl.java index f5bb05f86bc..17ccb1ddac1 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UMethodDecl.java +++ b/core/src/main/java/com/google/errorprone/refaster/UMethodDecl.java @@ -29,7 +29,7 @@ import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code MethodTree}. @@ -65,8 +65,7 @@ public Kind getKind() { } @Override - @Nullable - public Choice visitMethod(MethodTree decl, @Nullable Unifier unifier) { + public @Nullable Choice visitMethod(MethodTree decl, @Nullable Unifier unifier) { return getName() .unify(decl.getName(), unifier) .thenChoose(unifications(getReturnType(), decl.getReturnType())) diff --git a/core/src/main/java/com/google/errorprone/refaster/UMethodInvocation.java b/core/src/main/java/com/google/errorprone/refaster/UMethodInvocation.java index 1e85cf7f9b3..42223425fe7 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UMethodInvocation.java +++ b/core/src/main/java/com/google/errorprone/refaster/UMethodInvocation.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link MethodInvocationTree}. @@ -63,8 +63,7 @@ public static UMethodInvocation create(UExpression methodSelect, UExpression... public abstract ImmutableList getArguments(); @Override - @Nullable - public Choice visitMethodInvocation( + public @Nullable Choice visitMethodInvocation( MethodInvocationTree methodInvocation, @Nullable Unifier unifier) { return getMethodSelect() .unify(methodInvocation.getMethodSelect(), unifier) diff --git a/core/src/main/java/com/google/errorprone/refaster/UMethodType.java b/core/src/main/java/com/google/errorprone/refaster/UMethodType.java index 7b16cb327ba..21296c1b214 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UMethodType.java +++ b/core/src/main/java/com/google/errorprone/refaster/UMethodType.java @@ -23,7 +23,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.MethodType; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@code UType} representation of a {@link MethodType}. This can be used to e.g. disambiguate @@ -47,8 +47,7 @@ public static UMethodType create(UType returnType, List parameterTypes) { public abstract ImmutableList getParameterTypes(); @Override - @Nullable - public Choice visitMethodType(MethodType methodTy, @Nullable Unifier unifier) { + public @Nullable Choice visitMethodType(MethodType methodTy, @Nullable Unifier unifier) { // Don't unify the return type, which doesn't matter in overload resolution. return unifyList(unifier, getParameterTypes(), methodTy.getParameterTypes()); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UNewArray.java b/core/src/main/java/com/google/errorprone/refaster/UNewArray.java index 90e5138de4c..e5b4038abe8 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UNewArray.java +++ b/core/src/main/java/com/google/errorprone/refaster/UNewArray.java @@ -29,7 +29,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCNewArray; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** {@link UTree} version of {@link NewArrayTree}, which represents an array instantiation. */ @AutoValue @@ -45,21 +45,17 @@ public static UNewArray create( initializers != null ? ImmutableList.copyOf(initializers) : null); } - @Nullable @Override - public abstract UExpression getType(); + public abstract @Nullable UExpression getType(); - @Nullable @Override - public abstract ImmutableList getDimensions(); + public abstract @Nullable ImmutableList getDimensions(); - @Nullable @Override - public abstract ImmutableList getInitializers(); + public abstract @Nullable ImmutableList getInitializers(); @Override - @Nullable - public Choice visitNewArray(NewArrayTree newArray, @Nullable Unifier unifier) { + public @Nullable Choice visitNewArray(NewArrayTree newArray, @Nullable Unifier unifier) { boolean hasRepeated = getInitializers() != null && Iterables.any(getInitializers(), Predicates.instanceOf(URepeated.class)); diff --git a/core/src/main/java/com/google/errorprone/refaster/UNewClass.java b/core/src/main/java/com/google/errorprone/refaster/UNewClass.java index 78bfe7e7d8b..9d8b42dc4f6 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UNewClass.java +++ b/core/src/main/java/com/google/errorprone/refaster/UNewClass.java @@ -26,7 +26,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCNewClass; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link NewClassTree}, which represents a constructor invocation. @@ -60,8 +60,7 @@ public static UNewClass create(UExpression identifier, UExpression... arguments) } @Override - @Nullable - public abstract UExpression getEnclosingExpression(); + public abstract @Nullable UExpression getEnclosingExpression(); /** * Note: these are not the type arguments to the class, but to the constructor, for those @@ -78,12 +77,10 @@ public static UNewClass create(UExpression identifier, UExpression... arguments) public abstract ImmutableList getArguments(); @Override - @Nullable - public abstract UClassDecl getClassBody(); + public abstract @Nullable UClassDecl getClassBody(); @Override - @Nullable - public Choice visitNewClass(NewClassTree newClass, @Nullable Unifier unifier) { + public @Nullable Choice visitNewClass(NewClassTree newClass, @Nullable Unifier unifier) { return unifyNullable(unifier, getEnclosingExpression(), newClass.getEnclosingExpression()) .thenChoose(unifications(getTypeArguments(), newClass.getTypeArguments())) .thenChoose(unifications(getIdentifier(), newClass.getIdentifier())) diff --git a/core/src/main/java/com/google/errorprone/refaster/UOfKind.java b/core/src/main/java/com/google/errorprone/refaster/UOfKind.java index 68925519955..4398875aada 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UOfKind.java +++ b/core/src/main/java/com/google/errorprone/refaster/UOfKind.java @@ -24,7 +24,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.Set; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UExpression} imposing a restriction on the kind of the matched expression. @@ -57,8 +57,7 @@ public Kind getKind() { } @Override - @Nullable - protected Choice defaultAction(Tree tree, @Nullable Unifier unifier) { + protected @Nullable Choice defaultAction(Tree tree, @Nullable Unifier unifier) { return Choice.condition(allowed().contains(tree.getKind()), unifier) .thenChoose(unifications(expression(), tree)); } diff --git a/core/src/main/java/com/google/errorprone/refaster/URepeated.java b/core/src/main/java/com/google/errorprone/refaster/URepeated.java index 4a0d64c7691..6a7bd3fb2ed 100644 --- a/core/src/main/java/com/google/errorprone/refaster/URepeated.java +++ b/core/src/main/java/com/google/errorprone/refaster/URepeated.java @@ -21,7 +21,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** A variable that can match a sequence of expressions. */ @AutoValue @@ -35,8 +35,7 @@ public static URepeated create(CharSequence identifier, UExpression expression) abstract UExpression expression(); @Override - @Nullable - protected Choice defaultAction(Tree node, @Nullable Unifier unifier) { + protected @Nullable Choice defaultAction(Tree node, @Nullable Unifier unifier) { return expression().unify(node, unifier); } @@ -57,8 +56,7 @@ public Kind getKind() { } /** Gets the binding of the underlying identifier in the unifier. */ - @Nullable - public JCExpression getUnderlyingBinding(Unifier unifier) { + public @Nullable JCExpression getUnderlyingBinding(Unifier unifier) { return (unifier == null) ? null : unifier.getBinding(new UFreeIdent.Key(identifier())); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UReturn.java b/core/src/main/java/com/google/errorprone/refaster/UReturn.java index b9d748dd549..ef6e6bb6535 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UReturn.java +++ b/core/src/main/java/com/google/errorprone/refaster/UReturn.java @@ -22,7 +22,7 @@ import com.sun.source.tree.ReturnTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCReturn; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of a {@link ReturnTree}. @@ -36,8 +36,7 @@ public static UReturn create(UExpression expression) { } @Override - @Nullable - public abstract UExpression getExpression(); + public abstract @Nullable UExpression getExpression(); @Override public R accept(TreeVisitor visitor, D data) { @@ -55,8 +54,7 @@ public JCReturn inline(Inliner inliner) throws CouldNotResolveImportException { } @Override - @Nullable - public Choice visitReturn(ReturnTree ret, @Nullable Unifier unifier) { + public @Nullable Choice visitReturn(ReturnTree ret, @Nullable Unifier unifier) { return unifyNullable(unifier, getExpression(), ret.getExpression()); } } diff --git a/core/src/main/java/com/google/errorprone/refaster/UTemplater.java b/core/src/main/java/com/google/errorprone/refaster/UTemplater.java index b763484ba34..d33b3493791 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTemplater.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTemplater.java @@ -115,12 +115,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.MirroredTypeException; +import org.jspecify.annotations.Nullable; /** * Converts a type-checked syntax tree to a portable {@code UTree} template. @@ -212,8 +212,7 @@ public Tree template(Tree tree) { return tree.accept(this, null); } - @Nullable - private ImmutableList templateTrees(@Nullable Iterable trees) { + private @Nullable ImmutableList templateTrees(@Nullable Iterable trees) { if (trees == null) { return null; } @@ -254,8 +253,7 @@ public UExpression template(ExpressionTree tree) { return (UExpression) tree.accept(this, null); } - @Nullable - private ImmutableList templateExpressions( + private @Nullable ImmutableList templateExpressions( @Nullable Iterable expressions) { if (expressions == null) { return null; @@ -275,8 +273,7 @@ public UExpression templateType(Tree tree) { return template((ExpressionTree) tree); } - @Nullable - private ImmutableList templateTypeExpressions( + private @Nullable ImmutableList templateTypeExpressions( @Nullable Iterable types) { if (types == null) { return null; @@ -694,8 +691,7 @@ public UStatement template(StatementTree tree) { return (UStatement) tree.accept(this, null); } - @Nullable - private ImmutableList templateStatements( + private @Nullable ImmutableList templateStatements( @Nullable List statements) { if (statements == null) { return null; @@ -728,8 +724,7 @@ public UCatch visitCatch(CatchTree tree, Void v) { visitVariable(tree.getParameter(), null), visitBlock(tree.getBlock(), null)); } - @Nullable - private PlaceholderMethod placeholder(@Nullable ExpressionTree expr) { + private @Nullable PlaceholderMethod placeholder(@Nullable ExpressionTree expr) { Map placeholderMethods = context.get(RefasterRuleBuilderScanner.PLACEHOLDER_METHODS_KEY); return (placeholderMethods != null && expr != null) diff --git a/core/src/main/java/com/google/errorprone/refaster/UThrow.java b/core/src/main/java/com/google/errorprone/refaster/UThrow.java index 232fdedd6c3..8c48f25a032 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UThrow.java +++ b/core/src/main/java/com/google/errorprone/refaster/UThrow.java @@ -20,7 +20,7 @@ import com.sun.source.tree.ThrowTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCThrow; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} representation of a {@link ThrowTree}. @@ -47,8 +47,7 @@ public Kind getKind() { } @Override - @Nullable - public Choice visitThrow(ThrowTree throwStmt, @Nullable Unifier unifier) { + public @Nullable Choice visitThrow(ThrowTree throwStmt, @Nullable Unifier unifier) { return getExpression().unify(throwStmt.getExpression(), unifier); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UTree.java b/core/src/main/java/com/google/errorprone/refaster/UTree.java index 2846e303ccb..7414560eb12 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTree.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTree.java @@ -19,7 +19,7 @@ import com.sun.source.tree.Tree; import com.sun.source.util.SimpleTreeVisitor; import com.sun.tools.javac.tree.JCTree; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A serializable representation of a template syntax tree which can be unified with a target AST diff --git a/core/src/main/java/com/google/errorprone/refaster/UTry.java b/core/src/main/java/com/google/errorprone/refaster/UTry.java index 24884565973..58717be45f5 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTry.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTry.java @@ -24,7 +24,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCTry; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code TryTree}. @@ -52,8 +52,7 @@ static UTry create( public abstract ImmutableList getCatches(); @Override - @Nullable - public abstract UBlock getFinallyBlock(); + public abstract @Nullable UBlock getFinallyBlock(); @Override public Kind getKind() { @@ -77,8 +76,8 @@ public JCTry inline(Inliner inliner) throws CouldNotResolveImportException { } /** Skips the finally block if the result would be empty. */ - @Nullable - private JCBlock inlineFinallyBlock(Inliner inliner) throws CouldNotResolveImportException { + private @Nullable JCBlock inlineFinallyBlock(Inliner inliner) + throws CouldNotResolveImportException { if (getFinallyBlock() != null) { JCBlock block = getFinallyBlock().inline(inliner); if (!block.getStatements().isEmpty()) { @@ -89,8 +88,7 @@ private JCBlock inlineFinallyBlock(Inliner inliner) throws CouldNotResolveImport } @Override - @Nullable - public Choice visitTry(TryTree node, @Nullable Unifier unifier) { + public @Nullable Choice visitTry(TryTree node, @Nullable Unifier unifier) { return unifyList(unifier, getResources(), node.getResources()) .thenChoose(unifications(getBlock(), node.getBlock())) .thenChoose(unifications(getCatches(), node.getCatches())) diff --git a/core/src/main/java/com/google/errorprone/refaster/UType.java b/core/src/main/java/com/google/errorprone/refaster/UType.java index a9d0dd7ca19..c7115dd03c9 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UType.java +++ b/core/src/main/java/com/google/errorprone/refaster/UType.java @@ -18,7 +18,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A serializable representation of a type template, used for enforcing type constraints on target @@ -30,8 +30,7 @@ public abstract class UType extends Types.SimpleVisitor, Unifier implements Unifiable, Inlineable { @Override - @Nullable - public Choice visitType(Type t, @Nullable Unifier unifier) { + public @Nullable Choice visitType(Type t, @Nullable Unifier unifier) { return Choice.none(); } diff --git a/core/src/main/java/com/google/errorprone/refaster/UTypeApply.java b/core/src/main/java/com/google/errorprone/refaster/UTypeApply.java index 891b5a108f8..1795b6a05c6 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTypeApply.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTypeApply.java @@ -25,7 +25,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link ParameterizedTypeTree}. This is the AST version of {@link @@ -54,8 +54,7 @@ public static UTypeApply create(String type, UExpression... typeArguments) { public abstract ImmutableList getTypeArguments(); @Override - @Nullable - public Choice visitParameterizedType( + public @Nullable Choice visitParameterizedType( ParameterizedTypeTree typeApply, @Nullable Unifier unifier) { Choice choice = getType().unify(typeApply.getType(), unifier); if (getTypeArguments().isEmpty()) { diff --git a/core/src/main/java/com/google/errorprone/refaster/UTypeParameter.java b/core/src/main/java/com/google/errorprone/refaster/UTypeParameter.java index fd9e48ad2e7..6b440639cf7 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTypeParameter.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTypeParameter.java @@ -24,7 +24,7 @@ import com.sun.source.tree.TreeVisitor; import com.sun.source.tree.TypeParameterTree; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code TypeParameterTree}. @@ -73,8 +73,8 @@ public JCTypeParameter inline(Inliner inliner) throws CouldNotResolveImportExcep } @Override - @Nullable - public Choice visitTypeParameter(TypeParameterTree node, @Nullable Unifier unifier) { + public @Nullable Choice visitTypeParameter( + TypeParameterTree node, @Nullable Unifier unifier) { return getName() .unify(node.getName(), unifier) .thenChoose(unifications(getBounds(), node.getBounds())) diff --git a/core/src/main/java/com/google/errorprone/refaster/UTypeVar.java b/core/src/main/java/com/google/errorprone/refaster/UTypeVar.java index 357f2de1f48..1e5fa03c0b1 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTypeVar.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTypeVar.java @@ -24,7 +24,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type.TypeVar; import com.sun.tools.javac.tree.JCTree.JCExpression; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UType} version of {@link TypeVar}. @@ -54,8 +54,7 @@ public static TypeWithExpression create(Type type) { public abstract Type type(); - @Nullable - abstract JCExpression expression(); + abstract @Nullable JCExpression expression(); @Override public JCExpression inline(Inliner inliner) { diff --git a/core/src/main/java/com/google/errorprone/refaster/UTypeVarIdent.java b/core/src/main/java/com/google/errorprone/refaster/UTypeVarIdent.java index 5606b11ee37..7bec618d50f 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UTypeVarIdent.java +++ b/core/src/main/java/com/google/errorprone/refaster/UTypeVarIdent.java @@ -26,8 +26,8 @@ import com.sun.source.util.SimpleTreeVisitor; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree.JCExpression; -import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import org.jspecify.annotations.Nullable; /** * Identifier for a type variable in an AST; this is a syntactic representation of a {@link diff --git a/core/src/main/java/com/google/errorprone/refaster/UUnary.java b/core/src/main/java/com/google/errorprone/refaster/UUnary.java index 64109c7b637..84e80c61dc5 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UUnary.java +++ b/core/src/main/java/com/google/errorprone/refaster/UUnary.java @@ -32,7 +32,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.TreeCopier; import com.sun.tools.javac.tree.TreeMaker; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link UTree} version of {@link UnaryTree}. @@ -66,8 +66,7 @@ public static UUnary create(Kind unaryOp, UExpression expression) { public abstract UExpression getExpression(); @Override - @Nullable - public Choice visitUnary(UnaryTree unary, @Nullable Unifier unifier) { + public @Nullable Choice visitUnary(UnaryTree unary, @Nullable Unifier unifier) { return Choice.condition(getKind().equals(unary.getKind()), unifier) .thenChoose( unifications(getExpression(), ASTHelpers.stripParentheses(unary.getExpression()))); diff --git a/core/src/main/java/com/google/errorprone/refaster/UVariableDecl.java b/core/src/main/java/com/google/errorprone/refaster/UVariableDecl.java index 318abea6ceb..a5535b0735a 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UVariableDecl.java +++ b/core/src/main/java/com/google/errorprone/refaster/UVariableDecl.java @@ -30,7 +30,7 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Name; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@link UTree} representation of a local variable declaration. @@ -64,8 +64,7 @@ public static UVariableDecl create(CharSequence identifier, UExpression type) { public abstract UExpression getType(); @Override - @Nullable - public abstract UExpression getInitializer(); + public abstract @Nullable UExpression getInitializer(); ULocalVarIdent.Key key() { return new ULocalVarIdent.Key(getName()); diff --git a/core/src/main/java/com/google/errorprone/refaster/UWildcard.java b/core/src/main/java/com/google/errorprone/refaster/UWildcard.java index d21a77b828d..93920b987a2 100644 --- a/core/src/main/java/com/google/errorprone/refaster/UWildcard.java +++ b/core/src/main/java/com/google/errorprone/refaster/UWildcard.java @@ -27,7 +27,7 @@ import com.sun.source.tree.WildcardTree; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.tree.JCTree.JCWildcard; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * {@code UTree} representation of a {@code WildcardTree}. @@ -57,8 +57,7 @@ static UWildcard create(Kind kind, @Nullable UTree bound) { public abstract Kind getKind(); @Override - @Nullable - public abstract UTree getBound(); + public abstract @Nullable UTree getBound(); @Override public JCWildcard inline(Inliner inliner) throws CouldNotResolveImportException { diff --git a/core/src/main/java/com/google/errorprone/refaster/Unifier.java b/core/src/main/java/com/google/errorprone/refaster/Unifier.java index f1bb577fd98..7220bac1ea4 100644 --- a/core/src/main/java/com/google/errorprone/refaster/Unifier.java +++ b/core/src/main/java/com/google/errorprone/refaster/Unifier.java @@ -28,7 +28,7 @@ import com.sun.tools.javac.util.Context; import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A mutable representation of an attempt to match a template source tree against a target source @@ -71,8 +71,7 @@ public Inliner createInliner() { return new Inliner(context, bindings); } - @Nullable - public V getBinding(Bindings.Key key) { + public @Nullable V getBinding(Bindings.Key key) { return bindings.getBinding(key); } diff --git a/core/src/main/java/com/google/errorprone/refaster/annotation/RequiredAnnotationProcessor.java b/core/src/main/java/com/google/errorprone/refaster/annotation/RequiredAnnotationProcessor.java index 012a2960916..86aab6a100e 100644 --- a/core/src/main/java/com/google/errorprone/refaster/annotation/RequiredAnnotationProcessor.java +++ b/core/src/main/java/com/google/errorprone/refaster/annotation/RequiredAnnotationProcessor.java @@ -34,7 +34,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor7; import javax.tools.Diagnostic.Kind; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Enforces {@code @RequiredAnnotation} as an annotation processor. diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/inject/guice/testdata/InjectOnFinalFieldPositiveCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/inject/guice/testdata/InjectOnFinalFieldPositiveCases.java index f75e92e15c8..3bde324ed33 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/inject/guice/testdata/InjectOnFinalFieldPositiveCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/inject/guice/testdata/InjectOnFinalFieldPositiveCases.java @@ -17,7 +17,7 @@ package com.google.errorprone.bugpatterns.inject.guice.testdata; import com.google.inject.Inject; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author sgoldfeder@google.com (Steven Goldfeder) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitiveNegativeCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitiveNegativeCases.java index 2c3c1a39023..b1194bfa402 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitiveNegativeCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitiveNegativeCases.java @@ -16,7 +16,7 @@ package com.google.errorprone.bugpatterns.testdata; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author sebastian.h.monte@gmail.com (Sebastian Monte) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitivePositiveCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitivePositiveCases.java index e5169c81814..85bd3948e68 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitivePositiveCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/nullness/testdata/NullablePrimitivePositiveCases.java @@ -16,7 +16,7 @@ package com.google.errorprone.bugpatterns.testdata; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author sebastian.h.monte@gmail.com (Sebastian Monte) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/overloading/testdata/InconsistentOverloadsPositiveCasesAnnotations.java b/core/src/test/java/com/google/errorprone/bugpatterns/overloading/testdata/InconsistentOverloadsPositiveCasesAnnotations.java index 30e51303c18..2e7a698bf28 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/overloading/testdata/InconsistentOverloadsPositiveCasesAnnotations.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/overloading/testdata/InconsistentOverloadsPositiveCasesAnnotations.java @@ -16,7 +16,7 @@ package com.google.errorprone.bugpatterns.overloading.testdata; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public abstract class InconsistentOverloadsPositiveCasesAnnotations { diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/AsyncFunctionReturnsNullNegativeCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/AsyncFunctionReturnsNullNegativeCases.java index 7082b203bd2..84eca364344 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/AsyncFunctionReturnsNullNegativeCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/AsyncFunctionReturnsNullNegativeCases.java @@ -20,7 +20,7 @@ import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.ListenableFuture; import java.util.function.Supplier; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** Negative cases for {@link AsyncFunctionReturnsNull}. */ public class AsyncFunctionReturnsNullNegativeCases { diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases.java index 491dcf86594..1efa8f73def 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases.java @@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Tests for {@link BadImport}. diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases_expected.java b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases_expected.java index 23a169dd185..d45e21a541a 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases_expected.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/BadImportPositiveCases_expected.java @@ -16,7 +16,7 @@ package com.google.errorprone.bugpatterns.testdata; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Tests for {@link BadImport}. diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases.java b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases.java index ec69697a318..0a193c3014f 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author awturner@google.com (Andy Turner) @@ -186,7 +186,7 @@ static void positive_removeNullable_parameter(@Nullable Integer i) { static void positive_removeNullable_localVariable() { @Nullable Integer i = 0; - @javax.annotation.Nullable Integer j = 0; + @org.jspecify.annotations.Nullable Integer j = 0; int k = i + j; } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases_expected.java b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases_expected.java index 017edaf44b8..7ef2a847d3a 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases_expected.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/testdata/UnnecessaryBoxedVariableCases_expected.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.stream.Stream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** @author awturner@google.com (Andy Turner) */ class UnnecessaryBoxedVariableCases { diff --git a/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java b/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java index f7e3062a5b4..51286af9273 100644 --- a/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java +++ b/docgen/src/main/java/com/google/errorprone/BugPatternFileGenerator.java @@ -37,7 +37,7 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -62,7 +62,7 @@ class BugPatternFileGenerator implements LineProcessor> private final boolean generateFrontMatter; /** The base url for links to bugpatterns. */ - @Nullable private final String baseUrl; + private final @Nullable String baseUrl; public BugPatternFileGenerator( Path bugpatternDir, diff --git a/test_helpers/pom.xml b/test_helpers/pom.xml index 76fbfaf30d9..66b12d308c3 100644 --- a/test_helpers/pom.xml +++ b/test_helpers/pom.xml @@ -50,10 +50,10 @@ ${project.version} - - com.google.code.findbugs - jsr305 - 3.0.2 + + org.jspecify + jspecify + ${jspecify.version} diff --git a/test_helpers/src/main/java/com/google/errorprone/CompilationTestHelper.java b/test_helpers/src/main/java/com/google/errorprone/CompilationTestHelper.java index db1d1916e33..85c2c6e371e 100644 --- a/test_helpers/src/main/java/com/google/errorprone/CompilationTestHelper.java +++ b/test_helpers/src/main/java/com/google/errorprone/CompilationTestHelper.java @@ -54,9 +54,9 @@ import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; -import javax.annotation.Nullable; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.jspecify.annotations.Nullable; /** Helps test Error Prone bug checkers and compilations. */ @CheckReturnValue @@ -81,7 +81,7 @@ public class CompilationTestHelper { private final Class clazz; private final List sources = new ArrayList<>(); private ImmutableList extraArgs = ImmutableList.of(); - @Nullable private ImmutableList> overrideClasspath; + private @Nullable ImmutableList> overrideClasspath; private boolean expectNoDiagnostics = false; private Optional expectedResult = Optional.empty(); private LookForCheckNameInDiagnostic lookForCheckNameInDiagnostic = From 3ad4e45a97403014b6e72ad229170666cfcda939 Mon Sep 17 00:00:00 2001 From: cpovirk Date: Thu, 25 Jul 2024 17:56:49 -0700 Subject: [PATCH 17/39] Demonstrate that `NullableVoid` already handles `@Nullable` on constructors. PiperOrigin-RevId: 656164310 --- .../errorprone/bugpatterns/NullableVoidTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java index c51805a5a8e..03879bbcbbb 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/NullableVoidTest.java @@ -41,6 +41,19 @@ public void positive() { .doTest(); } + @Test + public void positiveConstructor() { + compilationHelper + .addSourceLines( + "Test.java", + "import javax.annotation.Nullable;", + "class Test {", + " // BUG: Diagnostic contains:", + " @Nullable Test() {}", + "}") + .doTest(); + } + @Test public void positiveCheckForNull() { compilationHelper From 40f5b218307425192cc2d1dbf10f47f2e07038de Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 26 Jul 2024 12:52:02 -0700 Subject: [PATCH 18/39] Verify that UnnecessaryBoxedVariable handles AutoValue factories PiperOrigin-RevId: 656498912 --- .../UnnecessaryBoxedVariableTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBoxedVariableTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBoxedVariableTest.java index 0657fafe4c0..8d021b6f646 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBoxedVariableTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBoxedVariableTest.java @@ -16,6 +16,8 @@ package com.google.errorprone.bugpatterns; +import com.google.auto.value.processor.AutoValueProcessor; +import com.google.common.collect.ImmutableList; import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.CompilationTestHelper; import org.junit.Test; @@ -87,4 +89,23 @@ public void lambdaReturn() { "}") .doTest(); } + + @Test + public void positiveFactory() { + compilationTestHelper + .addSourceLines( + "Foo.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Foo {", + " abstract int getFoo();", + " abstract boolean isBar();", + " // BUG: Diagnostic contains: int foo", + " static Foo create(Integer foo, Boolean bar) {", + " return new AutoValue_Foo(foo, bar);", + " }", + "}") + .setArgs(ImmutableList.of("-processor", AutoValueProcessor.class.getName())) + .doTest(); + } } From 98849112a3ea25451492cb82d48993bd389b7c9b Mon Sep 17 00:00:00 2001 From: cpovirk Date: Sat, 27 Jul 2024 07:06:03 -0700 Subject: [PATCH 19/39] Make `ASTHelpers.getSymbol(Tree)` delegate to the `MemberReferenceTree` overload. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This may or may not actually change behavior: The handling that I added in https://github.com/google/error-prone/commit/e5a6d0d8f9f96bda8e9952b7817cd0d2b63e51be was definitely important for `MethodInvocationTree`, since it affects behavior when code uses static imports. However, static imports can't be used with a `MemberReferenceTree`, so the change might not be important there. Still, as I noted in that previous CL's description, I get the impression that this might also help with cases like `someSerializable.equals`—and presumably `someSerializable::equals`, too. Anyway, this change seems clearly like either an improvement in behavior or a no-op that makes the implementation more consistent, so either way feels like a win. PiperOrigin-RevId: 656731744 --- .../main/java/com/google/errorprone/util/ASTHelpers.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index 814fafb2d4b..f164936a8c9 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -260,14 +260,13 @@ public static boolean sameVariable(ExpressionTree expr1, ExpressionTree expr2) { return ((JCIdent) tree).sym; } if (tree instanceof JCMethodInvocation) { - return ASTHelpers.getSymbol((MethodInvocationTree) tree); + return getSymbol((MethodInvocationTree) tree); } if (tree instanceof JCNewClass) { - return ASTHelpers.getSymbol((NewClassTree) tree); + return getSymbol((NewClassTree) tree); } if (tree instanceof MemberReferenceTree) { - // TODO: b/285157761 - Delegate to the MemberReferenceTree overload. - return ((JCMemberReference) tree).sym; + return getSymbol((MemberReferenceTree) tree); } if (tree instanceof JCAnnotatedType) { return getSymbol(((JCAnnotatedType) tree).underlyingType); From 8e39783f75b908e3455851124a6a42cf7cd32258 Mon Sep 17 00:00:00 2001 From: cpovirk Date: Sat, 27 Jul 2024 14:54:05 -0700 Subject: [PATCH 20/39] Automatic code cleanup. PiperOrigin-RevId: 656794179 --- .../main/java/com/google/errorprone/util/ASTHelpers.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index f164936a8c9..1460c59da53 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -2791,8 +2791,7 @@ public static boolean isRuleKind(CaseTree caseTree) { * Returns the statement or expression after the arrow for a {@link CaseTree} of the form: {@code * case -> }. */ - @Nullable - public static Tree getCaseTreeBody(CaseTree caseTree) { + public static @Nullable Tree getCaseTreeBody(CaseTree caseTree) { if (GET_CASE_BODY_METHOD == null) { return null; } @@ -2803,10 +2802,9 @@ public static Tree getCaseTreeBody(CaseTree caseTree) { } } - @Nullable private static final Method GET_CASE_BODY_METHOD = getGetCaseBodyMethod(); + private static final @Nullable Method GET_CASE_BODY_METHOD = getGetCaseBodyMethod(); - @Nullable - private static Method getGetCaseBodyMethod() { + private static @Nullable Method getGetCaseBodyMethod() { try { return CaseTree.class.getMethod("getBody"); } catch (NoSuchMethodException e) { From dbfd4a61c213f56b65df75c2857e9e1da018acf1 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Mon, 29 Jul 2024 16:01:08 -0700 Subject: [PATCH 21/39] Copy a key line from the `@Rule` Javadoc to[] https://junit.org/junit4/javadoc/4.13/org/junit/Rule.html#order() PiperOrigin-RevId: 657355882 --- .../google/errorprone/bugpatterns/DoNotUseRuleChain.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/DoNotUseRuleChain.java b/core/src/main/java/com/google/errorprone/bugpatterns/DoNotUseRuleChain.java index 657caa10955..aac2bb4dd0f 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/DoNotUseRuleChain.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/DoNotUseRuleChain.java @@ -50,9 +50,10 @@ @BugPattern( tags = {StandardTags.REFACTORING}, summary = - "Prefer using `@Rule` with an explicit order over declaring a `RuleChain`. " - + "RuleChain was the only way to declare ordered rules before JUnit 4.13. Newer " - + "versions should use the cleaner individual `@Rule(order = n)` option.", + "Prefer using `@Rule` with an explicit order over declaring a `RuleChain`. RuleChain was" + + " the only way to declare ordered rules before JUnit 4.13. Newer versions should use" + + " the cleaner individual `@Rule(order = n)` option. The rules with a higher value are" + + " inner.", severity = WARNING) public class DoNotUseRuleChain extends BugChecker implements VariableTreeMatcher { From 198903cfd48f10da3a2036266db638f31db57816 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 30 Jul 2024 08:16:50 -0700 Subject: [PATCH 22/39] Recognize more unnecessary breaks in UnnecessaryBreakInSwitch PiperOrigin-RevId: 657598108 --- .../bugpatterns/UnnecessaryBreakInSwitch.java | 52 +++++++++++++------ .../UnnecessaryBreakInSwitchTest.java | 51 +++++++++++++++++- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java index 8deb44f59fc..0aeba268bf0 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitch.java @@ -21,6 +21,7 @@ import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.isRuleKind; +import com.google.common.collect.ImmutableList; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.fixes.SuggestedFix; @@ -29,8 +30,9 @@ import com.sun.source.tree.BlockTree; import com.sun.source.tree.BreakTree; import com.sun.source.tree.CaseTree; -import com.sun.source.tree.StatementTree; +import com.sun.source.tree.IfTree; import com.sun.source.tree.Tree; +import com.sun.source.util.SimpleTreeVisitor; /** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */ @BugPattern( @@ -43,21 +45,41 @@ public Description matchCase(CaseTree tree, VisitorState state) { return NO_MATCH; } Tree body = ASTHelpers.getCaseTreeBody(tree); - if (!(body instanceof BlockTree)) { + ImmutableList unnecessaryBreaks = unnecessaryBreaks(body); + if (unnecessaryBreaks.isEmpty()) { return NO_MATCH; } - BlockTree blockTree = (BlockTree) body; - if (blockTree.getStatements().isEmpty()) { - return NO_MATCH; - } - StatementTree last = getLast(blockTree.getStatements()); - if (!(last instanceof BreakTree)) { - return NO_MATCH; - } - BreakTree breakTree = (BreakTree) last; - if (breakTree.getLabel() != null) { - return NO_MATCH; - } - return describeMatch(last, SuggestedFix.delete(last)); + unnecessaryBreaks.forEach( + unnecessaryBreak -> + state.reportMatch( + describeMatch(unnecessaryBreak, SuggestedFix.delete(unnecessaryBreak)))); + return NO_MATCH; + } + + private ImmutableList unnecessaryBreaks(Tree tree) { + ImmutableList.Builder result = ImmutableList.builder(); + new SimpleTreeVisitor() { + @Override + public Void visitBreak(BreakTree node, Void unused) { + if (node.getLabel() == null) { + result.add(node); + } + return null; + } + + @Override + public Void visitBlock(BlockTree node, Void unused) { + visit(getLast(node.getStatements(), null), null); + return null; + } + + @Override + public Void visitIf(IfTree node, Void unused) { + visit(node.getThenStatement(), null); + visit(node.getElseStatement(), null); + return null; + } + }.visit(tree, null); + return result.build(); } } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java index 9e3ed40d0af..49da24c7c46 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/UnnecessaryBreakInSwitchTest.java @@ -92,7 +92,10 @@ public void negativeNotLast() { " void f(int i) {", " switch (i) {", " default -> {", - " if (true) break;", + " if (true) {", + " break;", + " }", + " System.err.println();", " }", " };", " }", @@ -120,4 +123,50 @@ public void negativeLabelledBreak() { "}") .doTest(); } + + @Test + public void negativeLoop() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " while (true) {", + " switch (i) {", + " default -> {", + " while (true) {", + " break;", + " }", + " }", + " }", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void positiveIf() { + assumeTrue(RuntimeVersion.isAtLeast14()); + testHelper + .addSourceLines( + "Test.java", + "class Test {", + " void f(int i) {", + " switch (i) {", + " default -> {", + " if (true) {", + " // BUG: Diagnostic contains: break is unnecessary", + " break;", + " } else {", + " // BUG: Diagnostic contains: break is unnecessary", + " break;", + " }", + " }", + " };", + " }", + "}") + .doTest(); + } } From 5413aa59a690e17135238961b232a846365f7d02 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 30 Jul 2024 08:43:06 -0700 Subject: [PATCH 23/39] Recognize AutoValue extension generated code in ClassInitializationDeadlock and AutoValueSubclassLeaked PiperOrigin-RevId: 657606731 --- .../bugpatterns/AutoValueSubclassLeaked.java | 5 +++- .../ClassInitializationDeadlock.java | 6 +++- .../AutoValueSubclassLeakedTest.java | 29 +++++++++++++++++++ .../ClassInitializationDeadlockTest.java | 17 +++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java index 60ecfe2b9f4..d43274e0cae 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java @@ -39,6 +39,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Type; +import java.util.regex.Pattern; /** Matches {@code AutoValue_} uses outside the containing file. */ @BugPattern( @@ -55,6 +56,8 @@ public final class AutoValueSubclassLeaked extends BugChecker implements CompilationUnitTreeMatcher { + private static final Pattern AUTO_VALUE_PREFIX = Pattern.compile("\\$*AutoValue_.*"); + @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { ImmutableSet autoValueClassesFromThisFile = findAutoValueClasses(tree, state); @@ -91,7 +94,7 @@ public Void visitIdentifier(IdentifierTree identifierTree, Void unused) { private void handle(Tree tree) { Symbol symbol = getSymbol(tree); if (symbol instanceof ClassSymbol - && symbol.getSimpleName().toString().startsWith("AutoValue_") + && AUTO_VALUE_PREFIX.matcher(symbol.getSimpleName().toString()).matches() && autoValueClassesFromThisFile.stream() .noneMatch(av -> isSubtype(symbol.type, av, state))) { state.reportMatch(describeMatch(tree)); diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlock.java b/core/src/main/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlock.java index ea569ddb2c3..7b9696c310d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlock.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlock.java @@ -48,11 +48,15 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; +import java.util.regex.Pattern; import javax.lang.model.element.ElementKind; /** See the summary. */ @BugPattern(summary = "Possible class initialization deadlock", severity = WARNING) public class ClassInitializationDeadlock extends BugChecker implements BugChecker.ClassTreeMatcher { + + private static final Pattern AUTO_VALUE_PREFIX = Pattern.compile("\\$*AutoValue_.*"); + @Override public Description matchClass(ClassTree tree, VisitorState state) { ClassSymbol classSymbol = getSymbol(tree); @@ -211,7 +215,7 @@ boolean nonPrivateInstantiator(ClassSymbol use) { // methods), it can't be directly instantiated outside the current file. return false; } - if (use.getSimpleName().toString().startsWith("AutoValue_")) { + if (AUTO_VALUE_PREFIX.matcher(use.getSimpleName().toString()).matches()) { // AutoValue generated code is necessarily package-private, but should only be accessed // within the declaration of the corresponding base class. See also the discussion of // AutoValue in diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java index bf8061ff4b1..1c5ffe7fd06 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java @@ -124,4 +124,33 @@ public void generatedCode() { "}") .doTest(); } + + @Test + public void positiveAutoValueExtension() { + helper + .addSourceLines( + "$$AutoValue_Foo.java", // + "package test;", + "class $$AutoValue_Foo extends Test.Foo {", + "}") + .addSourceLines( + "Test.java", + "package test;", + "import com.google.auto.value.AutoValue;", + "class Test {", + " @AutoValue", + " abstract static class Foo {", + " }", + "}") + .addSourceLines( + "Bar.java", + "package test;", + "class Bar {", + " public static Test.Foo create() {", + " // BUG: Diagnostic contains:", + " return new $$AutoValue_Foo();", + " }", + "}") + .doTest(); + } } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlockTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlockTest.java index b1c51f7f1d1..ab3f981a75b 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlockTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/ClassInitializationDeadlockTest.java @@ -309,4 +309,21 @@ public void nonNestedSubclass() { "class B extends A {}") .doTest(); } + + @Test + public void negativeAutoValueExtension() { + testHelper + .addSourceLines( + "$$AutoValue_Foo.java", // + "class $$AutoValue_Foo extends Foo {", + "}") + .addSourceLines( + "A.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Foo {", + " private static final Foo FOO = new $$AutoValue_Foo();", + "}") + .doTest(); + } } From ca7b569a74d3f469036c3468f1685e87c2e6d7c6 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 30 Jul 2024 13:40:44 -0700 Subject: [PATCH 24/39] Improve handling of AutoValue extensions in AutoValueSubclassLeaked Follow-up to https://github.com/google/error-prone/commit/5413aa59a690e17135238961b232a846365f7d02 Don't rely on the extension generated code having `@Generated` annotations. A number of extensions omit the annotation, and AutoValueSubclassLeaked relies on the generated annotation to recognize generated code, so after https://github.com/google/error-prone/commit/5413aa59a690e17135238961b232a846365f7d02 it was reporting errors for references from extensions. PiperOrigin-RevId: 657715233 --- .../bugpatterns/AutoValueSubclassLeaked.java | 17 +++++++- .../AutoValueSubclassLeakedTest.java | 39 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java index d43274e0cae..5a8b6c67f47 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeaked.java @@ -23,7 +23,6 @@ import static com.google.errorprone.util.ASTHelpers.hasAnnotation; import static com.google.errorprone.util.ASTHelpers.isSubtype; -import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; @@ -39,6 +38,7 @@ import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; import java.util.regex.Pattern; /** Matches {@code AutoValue_} uses outside the containing file. */ @@ -58,6 +58,8 @@ public final class AutoValueSubclassLeaked extends BugChecker private static final Pattern AUTO_VALUE_PREFIX = Pattern.compile("\\$*AutoValue_.*"); + private static final String AUTO_VALUE_ANNOTATION = "com.google.auto.value.AutoValue"; + @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { ImmutableSet autoValueClassesFromThisFile = findAutoValueClasses(tree, state); @@ -110,9 +112,20 @@ private static ImmutableSet findAutoValueClasses( new TreeScanner() { @Override public Void visitClass(ClassTree classTree, Void unused) { - if (hasAnnotation(classTree, AutoValue.class, state)) { + if (hasAnnotation(classTree, AUTO_VALUE_ANNOTATION, state)) { types.add(getType(classTree)); } + ClassSymbol classSymbol = getSymbol(classTree); + if (AUTO_VALUE_PREFIX.matcher(classSymbol.getSimpleName().toString()).matches()) { + for (Type type = classSymbol.asType(); + !type.hasTag(TypeTag.NONE); + type = state.getTypes().supertype(type)) { + if (hasAnnotation(type.asElement(), AUTO_VALUE_ANNOTATION, state)) { + types.add(type); + break; + } + } + } return super.visitClass(classTree, null); } }, diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java index 1c5ffe7fd06..e4ffec7eda6 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueSubclassLeakedTest.java @@ -153,4 +153,43 @@ public void positiveAutoValueExtension() { "}") .doTest(); } + + @Test + public void positiveAutoValueExtension_noGeneratedAnnotation() { + CompilationTestHelper.newInstance(AutoValueSubclassLeaked.class, getClass()) + .addSourceLines( + "$AutoValue_Test_Foo.java", // + "package test;", + "final class AutoValue_Test_Foo extends $AutoValue_Test_Foo {", + " static final Test.Foo AUTO_VALUE = new AutoValue_Test_Foo();", + "}") + .addSourceLines( + "$AutoValue_Test_Foo.java", // + "package test;", + "import javax.annotation.processing.Generated;", + "@Generated(\"com.google.auto.value.processor.AutoValueProcessor\")", + "class $AutoValue_Test_Foo extends Test.Foo {", + "}") + .addSourceLines( + "Test.java", + "package test;", + "import com.google.auto.value.AutoValue;", + "class Test {", + " static final Test.Foo EXTENSION = new $AutoValue_Test_Foo();", + " static final Test.Foo AUTO_VALUE = new AutoValue_Test_Foo();", + " @AutoValue", + " abstract static class Foo {", + " }", + "}") + .addSourceLines( + "Bar.java", + "package test;", + "class Bar {", + " // BUG: Diagnostic contains:", + " static final Test.Foo EXTENSION = new $AutoValue_Test_Foo();", + " // BUG: Diagnostic contains:", + " static final Test.Foo AUTO_VALUE = new AutoValue_Test_Foo();", + "}") + .doTest(); + } } From f56c8b2450fed455910a182d6d3abf498e59527c Mon Sep 17 00:00:00 2001 From: cpovirk Date: Wed, 31 Jul 2024 05:36:04 -0700 Subject: [PATCH 25/39] Confirm that https://errorprone.info/bugpattern/CannotMockFinalClass covers records. Relevant to https://github.com/google/error-prone/pull/4384. PiperOrigin-RevId: 657972168 --- .../bugpatterns/CannotMockFinalClassTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/CannotMockFinalClassTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/CannotMockFinalClassTest.java index dd6264f25db..e22105d689e 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/CannotMockFinalClassTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/CannotMockFinalClassTest.java @@ -16,7 +16,10 @@ package com.google.errorprone.bugpatterns; +import static org.junit.Assume.assumeTrue; + import com.google.errorprone.CompilationTestHelper; +import com.google.errorprone.util.RuntimeVersion; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -36,6 +39,26 @@ public void positiveCase() { compilationHelper.addSourceFile("CannotMockFinalClassPositiveCases.java").doTest(); } + @Test + public void positiveCase_record() { + assumeTrue(RuntimeVersion.isAtLeast16()); + + compilationHelper + .addSourceLines( + "Test.java", + "import org.junit.runner.RunWith;", + "import org.junit.runners.JUnit4;", + "import org.mockito.Mock;", + "import org.mockito.Mockito;", + "@RunWith(JUnit4.class)", + "public class Test {", + " record Record() {}", + " // BUG: Diagnostic contains: ", + " @Mock Record record;", + "}") + .doTest(); + } + @Test public void negativeCase() { compilationHelper.addSourceFile("CannotMockFinalClassNegativeCases.java").doTest(); From ba7e3b3a4d1c2baf8f8126b60a7d68f32205ba9a Mon Sep 17 00:00:00 2001 From: Nick Glorioso Date: Wed, 31 Jul 2024 14:35:15 -0700 Subject: [PATCH 26/39] Internal Change PiperOrigin-RevId: 658145394 --- .../main/java/com/google/errorprone/refaster/RefasterRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/errorprone/refaster/RefasterRule.java b/core/src/main/java/com/google/errorprone/refaster/RefasterRule.java index bb245d1524f..232139b21cd 100644 --- a/core/src/main/java/com/google/errorprone/refaster/RefasterRule.java +++ b/core/src/main/java/com/google/errorprone/refaster/RefasterRule.java @@ -122,7 +122,7 @@ public abstract class RefasterRule typeVariables(); From 6ff6f35971b95aa449fefc1633529679f04d0c25 Mon Sep 17 00:00:00 2001 From: Mark Hansen Date: Wed, 31 Jul 2024 20:52:49 -0700 Subject: [PATCH 27/39] Allow any package's @CanIgnoreReturnValue to suppress CanIgnoreReturnValueSuggester This brings it into alignment with the CheckReturnValue checker logic. PiperOrigin-RevId: 658248419 --- .../CanIgnoreReturnValueSuggester.java | 19 ++++++---- .../CanIgnoreReturnValueSuggesterTest.java | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java index 4230cf94aed..404f4ec37c5 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java @@ -25,6 +25,7 @@ import static com.google.errorprone.util.ASTHelpers.getReturnType; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.hasAnnotation; +import static com.google.errorprone.util.ASTHelpers.hasDirectAnnotationWithSimpleName; import static com.google.errorprone.util.ASTHelpers.isAbstract; import static com.google.errorprone.util.ASTHelpers.isSameType; import static com.google.errorprone.util.ASTHelpers.isSubtype; @@ -78,10 +79,9 @@ public final class CanIgnoreReturnValueSuggester extends BugChecker implements M private static final String AUTO_VALUE = "com.google.auto.value.AutoValue"; private static final String IMMUTABLE = "com.google.errorprone.annotations.Immutable"; - private static final String CIRV = "com.google.errorprone.annotations.CanIgnoreReturnValue"; + private static final String CIRV_SIMPLE_NAME = "CanIgnoreReturnValue"; private static final ImmutableSet EXEMPTING_METHOD_ANNOTATIONS = ImmutableSet.of( - CIRV, "com.google.errorprone.annotations.CheckReturnValue", "com.google.errorprone.refaster.annotation.AfterTemplate"); @@ -118,7 +118,12 @@ public Description matchMethod(MethodTree methodTree, VisitorState state) { return Description.NO_MATCH; } - // If the method has an exempting annotation, then bail out. + // If the method is @CanIgnoreReturnValue (in any package), then bail out. + if (hasDirectAnnotationWithSimpleName(methodSymbol, CIRV_SIMPLE_NAME)) { + return Description.NO_MATCH; + } + + // If the method has another exempting annotation, then bail out. if (exemptingMethodAnnotations.stream() .anyMatch(annotation -> hasAnnotation(methodSymbol, annotation, state))) { return Description.NO_MATCH; @@ -187,7 +192,8 @@ private Description annotateWithCanIgnoreReturnValue(MethodTree methodTree, Visi } // now annotate it with @CanIgnoreReturnValue - fix.prefixWith(methodTree, "@" + qualifyType(state, fix, CIRV) + "\n"); + String cirv = "com.google.errorprone.annotations.CanIgnoreReturnValue"; + fix.prefixWith(methodTree, "@" + qualifyType(state, fix, cirv) + "\n"); return describeMatch(methodTree, fix.build()); } @@ -307,9 +313,8 @@ private boolean isIgnorableMethodCallOnSameInstance( || isIdentifier(receiver, "super")) { // If the method we're calling is @CIRV and the enclosing class could be represented by // the object being returned by the other method, then it's probable that the other - // method is likely to - // be an ignorable result. - return hasAnnotation(calledMethod, CIRV, state) + // method is likely to be an ignorable result. + return hasDirectAnnotationWithSimpleName(calledMethod, CIRV_SIMPLE_NAME) && isSubtype(enclosingClassType, methodReturnType, state) && isSubtype(enclosingClassType, getReturnType(mit), state); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggesterTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggesterTest.java index 1c9a88c1b49..a5f1606036d 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggesterTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggesterTest.java @@ -418,6 +418,43 @@ public void simpleCaseAlreadyAnnotatedWithCirv() { .doTest(); } + @Test + public void alreadyAnnotatedWithProtobufCirv_b356526159() { + helper + .addInputLines( + "Client.java", + "package com.google.protobuf;", + "public final class Client {", + " private String name;", + " @CanIgnoreReturnValue", + " public Client setName(String name) {", + " this.name = name;", + " return this;", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void alreadyAnnotatedWithArbitraryCirv_b356526159() { + helper + .addInputLines( + "Client.java", + "package com.google.frobber;", + "public final class Client {", + " @interface CanIgnoreReturnValue {}", + " private String name;", + " @CanIgnoreReturnValue", + " public Client setName(String name) {", + " this.name = name;", + " return this;", + " }", + "}") + .expectUnchanged() + .doTest(); + } + @Test public void simpleCaseAlreadyAnnotatedWithCrv() { helper From 1e0e03c59ba35ba894765fe51ba0c9f064ad86b9 Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Thu, 1 Aug 2024 08:24:23 -0700 Subject: [PATCH 28/39] Don't fire `CanIgnoreReturnValueSuggester` if the function is directly annotated w/ `@CheckReturnValue` (from any package). #checkreturnvalue PiperOrigin-RevId: 658409445 --- .../CanIgnoreReturnValueSuggester.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java index 404f4ec37c5..15fd049c4cd 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java @@ -79,11 +79,10 @@ public final class CanIgnoreReturnValueSuggester extends BugChecker implements M private static final String AUTO_VALUE = "com.google.auto.value.AutoValue"; private static final String IMMUTABLE = "com.google.errorprone.annotations.Immutable"; + private static final String CRV_SIMPLE_NAME = "CheckReturnValue"; private static final String CIRV_SIMPLE_NAME = "CanIgnoreReturnValue"; private static final ImmutableSet EXEMPTING_METHOD_ANNOTATIONS = - ImmutableSet.of( - "com.google.errorprone.annotations.CheckReturnValue", - "com.google.errorprone.refaster.annotation.AfterTemplate"); + ImmutableSet.of("com.google.errorprone.refaster.annotation.AfterTemplate"); private static final ImmutableSet EXEMPTING_CLASS_ANNOTATIONS = ImmutableSet.of( @@ -112,14 +111,14 @@ public final class CanIgnoreReturnValueSuggester extends BugChecker implements M @Override public Description matchMethod(MethodTree methodTree, VisitorState state) { MethodSymbol methodSymbol = getSymbol(methodTree); - // Don't fire on overrides of methods within anonymous classes. - if (streamSuperMethods(methodSymbol, state.getTypes()).findFirst().isPresent() - && methodSymbol.owner.isAnonymous()) { + + // If the method is directly annotated w/ any @CanIgnoreReturnValue, then bail out. + if (hasDirectAnnotationWithSimpleName(methodSymbol, CIRV_SIMPLE_NAME)) { return Description.NO_MATCH; } - // If the method is @CanIgnoreReturnValue (in any package), then bail out. - if (hasDirectAnnotationWithSimpleName(methodSymbol, CIRV_SIMPLE_NAME)) { + // If the method is directly annotated w/ any @CheckReturnValue, then bail out. + if (hasDirectAnnotationWithSimpleName(methodSymbol, CRV_SIMPLE_NAME)) { return Description.NO_MATCH; } @@ -141,6 +140,12 @@ public Description matchMethod(MethodTree methodTree, VisitorState state) { return Description.NO_MATCH; } + // Don't fire on overrides of methods within anonymous classes. + if (streamSuperMethods(methodSymbol, state.getTypes()).findFirst().isPresent() + && methodSymbol.owner.isAnonymous()) { + return Description.NO_MATCH; + } + // if the method always return a single input param (of the same type), make it CIRV if (methodAlwaysReturnsInputParam(methodTree, state)) { return annotateWithCanIgnoreReturnValue(methodTree, state); From 4ec7faac227e448b4490fe51dcb5c66bbb4f1bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Thu, 1 Aug 2024 09:46:00 -0700 Subject: [PATCH 29/39] Minor documentation tweak in `SuggestedFix.Builder`. PiperOrigin-RevId: 658434707 --- .../java/com/google/errorprone/fixes/SuggestedFix.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java index 2a958d1814a..feabf10f910 100644 --- a/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java +++ b/check_api/src/main/java/com/google/errorprone/fixes/SuggestedFix.java @@ -294,7 +294,7 @@ public Builder swap(Tree node1, Tree node2) { /** * Add an import statement as part of this SuggestedFix. Import string should be of the form - * "foo.bar.baz". + * "foo.bar.SomeClass". */ @CanIgnoreReturnValue public Builder addImport(String importString) { @@ -304,7 +304,7 @@ public Builder addImport(String importString) { /** * Add a static import statement as part of this SuggestedFix. Import string should be of the - * form "foo.bar.baz". + * form "foo.bar.SomeClass.someMethod" or "foo.bar.SomeClass.SOME_FIELD". */ @CanIgnoreReturnValue public Builder addStaticImport(String importString) { @@ -314,7 +314,7 @@ public Builder addStaticImport(String importString) { /** * Remove an import statement as part of this SuggestedFix. Import string should be of the form - * "foo.bar.baz". + * "foo.bar.SomeClass". */ @CanIgnoreReturnValue public Builder removeImport(String importString) { @@ -324,7 +324,7 @@ public Builder removeImport(String importString) { /** * Remove a static import statement as part of this SuggestedFix. Import string should be of the - * form "foo.bar.baz". + * form "foo.bar.SomeClass.someMethod" or "foo.bar.SomeClass.SOME_FIELD". */ @CanIgnoreReturnValue public Builder removeStaticImport(String importString) { From fe072361840715ae907b28dee66f2c732c255551 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Thu, 1 Aug 2024 12:14:01 -0700 Subject: [PATCH 30/39] Add Error Prone check for unnecessary boxed types in AutoValue classes. This check will warn users when they are using boxed types in their AutoValue classes that are not Nullable. This is because boxed types are not necessary in AutoValue classes, and they can actually be harmful because they can cause unnecessary boxing and unboxing. Clean up reference: unknown commit This check is currently enabled as a warning, but it can be made into an error in the future. PiperOrigin-RevId: 658493053 --- .../bugpatterns/AutoValueBoxedValues.java | 325 ++++++++++ .../scanner/BuiltInCheckerSuppliers.java | 2 + .../bugpatterns/AutoValueBoxedValuesTest.java | 590 ++++++++++++++++++ docs/bugpattern/AutoValueBoxedValues.md | 11 + 4 files changed, 928 insertions(+) create mode 100644 core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java create mode 100644 core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java create mode 100644 docs/bugpattern/AutoValueBoxedValues.md diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java new file mode 100644 index 00000000000..6b57f5f6989 --- /dev/null +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java @@ -0,0 +1,325 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.matchers.Description.NO_MATCH; +import static com.google.errorprone.matchers.Matchers.hasModifier; +import static com.google.errorprone.util.ASTHelpers.getSymbol; +import static java.beans.Introspector.decapitalize; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; +import com.google.errorprone.dataflow.nullnesspropagation.Nullness; +import com.google.errorprone.dataflow.nullnesspropagation.NullnessAnnotations; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ReturnTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; +import java.util.List; +import java.util.Optional; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; + +/** See summary for details. */ +@BugPattern( + summary = + "AutoValue instances should not usually contain boxed types that are not Nullable. We" + + " recommend removing the unnecessary boxing.", + severity = WARNING) +public class AutoValueBoxedValues extends BugChecker implements ClassTreeMatcher { + private static final Matcher ABSTRACT_MATCHER = hasModifier(Modifier.ABSTRACT); + private static final Matcher STATIC_MATCHER = hasModifier(Modifier.STATIC); + + @Override + public Description matchClass(ClassTree tree, VisitorState state) { + if (!ASTHelpers.hasAnnotation(tree, AutoValue.class.getName(), state)) { + return NO_MATCH; + } + + // Identify and potentially fix the getters. + ImmutableList getters = handleGetterMethods(tree, state); + + // If we haven't modified any getter, it's ok to stop. + if (getters.stream().allMatch(getter -> getter.fix().isEmpty())) { + return NO_MATCH; + } + + // Handle the Builder class, if there is one. + boolean builderFound = false; + for (Tree memberTree : tree.getMembers()) { + if (memberTree instanceof ClassTree + && ASTHelpers.hasAnnotation(memberTree, AutoValue.Builder.class.getName(), state)) { + handleSetterMethods((ClassTree) memberTree, state, getters); + builderFound = true; + break; + } + } + + // If a builder was not found, handle the factory methods. + if (!builderFound) { + handleFactoryMethods(tree, state, getters); + } + + getters.stream() + .filter(getter -> !getter.fix().isEmpty()) + .forEach(getter -> state.reportMatch(describeMatch(getter.method(), getter.fix().build()))); + + return NO_MATCH; + } + + /** + * Returns the {@link List} of {@link Getter} in the {@link AutoValue} class along with the fixes + * to be applied. + * + * @param classTree The {@link AutoValue} class tree. + * @param state The visitor state. + * @return The list of {@link Getter} in the class. + */ + private ImmutableList handleGetterMethods(ClassTree classTree, VisitorState state) { + return classTree.getMembers().stream() + .filter(MethodTree.class::isInstance) + .map(memberTree -> (MethodTree) memberTree) + .filter( + methodTree -> + ABSTRACT_MATCHER.matches(methodTree, state) && methodTree.getParameters().isEmpty()) + .map(methodTree -> maybeFixGetter(methodTree, state)) + .collect(toImmutableList()); + } + + /** + * Converts the {@link MethodTree} of a getter to a {@link Getter}. If the getter needs to be + * fixed, it returns a {@link Getter} with a non-empty {@link SuggestedFix}. + */ + private Getter maybeFixGetter(MethodTree method, VisitorState state) { + Getter getter = Getter.of(method); + Type type = ASTHelpers.getType(method.getReturnType()); + if (!isSuppressed(method, state) + && !hasNullableAnnotation(method) + && isBoxedPrimitive(state, type)) { + suggestRemoveUnnecessaryBoxing(method.getReturnType(), state, type, getter.fix()); + } + return getter; + } + + /** + * Identifies and fixes the setters in the {@link AutoValue.Builder} class. + * + * @param classTree The {@link AutoValue.Builder} class tree. + * @param state The visitor state. + * @param getters The {@link List} of {@link Getter} in the {@link AutoValue} class. + */ + private void handleSetterMethods(ClassTree classTree, VisitorState state, List getters) { + classTree.getMembers().stream() + .filter(MethodTree.class::isInstance) + .map(memberTree -> (MethodTree) memberTree) + .filter( + methodTree -> + ABSTRACT_MATCHER.matches(methodTree, state) + && methodTree.getParameters().size() == 1) + .forEach(methodTree -> maybeFixSetter(methodTree, state, getters)); + } + + /** Given a setter, it tries to apply a fix if the corresponding getter was also fixed. */ + private void maybeFixSetter(MethodTree methodTree, VisitorState state, List getters) { + if (isSuppressed(methodTree, state)) { + return; + } + boolean allGettersPrefixed = allGettersPrefixed(getters); + Optional fixedGetter = + getters.stream() + .filter( + getter -> + !getter.fix().isEmpty() + && matchGetterAndSetter(getter.method(), methodTree, allGettersPrefixed)) + .findAny(); + if (fixedGetter.isPresent()) { + var parameter = methodTree.getParameters().get(0); + Type type = ASTHelpers.getType(parameter); + if (isBoxedPrimitive(state, type) && !hasNullableAnnotation(parameter)) { + suggestRemoveUnnecessaryBoxing(parameter.getType(), state, type, fixedGetter.get().fix()); + } + } + } + + /** + * Identifies and fixes the factory method in the {@link AutoValue} class. + * + *

    This method only handles the case of "trivial" factory methods, i.e. methods that have one + * argument for each getter in the class and contains a single return statement passing all the + * arguments to the constructor in the same order. + * + * @param classTree The {@link AutoValue} class tree. + * @param state The visitor state. + * @param getters The {@link List} of {@link Getter} in the {@link AutoValue} class. + */ + private void handleFactoryMethods(ClassTree classTree, VisitorState state, List getters) { + Optional trivialFactoryMethod = + classTree.getMembers().stream() + .filter(MethodTree.class::isInstance) + .map(memberTree -> (MethodTree) memberTree) + .filter( + methodTree -> + STATIC_MATCHER.matches(methodTree, state) + && ASTHelpers.isSameType( + ASTHelpers.getType(methodTree.getReturnType()), + ASTHelpers.getType(classTree), + state) + && isTrivialFactoryMethod(methodTree, getters.size())) + .findAny(); + if (trivialFactoryMethod.isEmpty()) { + return; + } + for (int idx = 0; idx < getters.size(); idx++) { + Getter getter = getters.get(idx); + if (!getter.fix().isEmpty()) { + var parameter = trivialFactoryMethod.get().getParameters().get(idx); + Type type = ASTHelpers.getType(parameter); + if (isBoxedPrimitive(state, type) && !hasNullableAnnotation(parameter)) { + suggestRemoveUnnecessaryBoxing(parameter.getType(), state, type, getter.fix()); + } + } + } + } + + /** Returns true if the given tree has a {@code Nullable} annotation. */ + private static boolean hasNullableAnnotation(Tree tree) { + return NullnessAnnotations.fromAnnotationsOn(getSymbol(tree)).orElse(null) == Nullness.NULLABLE; + } + + /** Returns the primitive type corresponding to a boxed type. */ + private static Type unbox(VisitorState state, Type type) { + return state.getTypes().unboxedType(type); + } + + /** Returns true if the value of {@link Type} is a boxed primitive. */ + private static boolean isBoxedPrimitive(VisitorState state, Type type) { + if (type.isPrimitive()) { + return false; + } + Type unboxed = unbox(state, type); + return unboxed != null && unboxed.getTag() != TypeTag.NONE && unboxed.getTag() != TypeTag.VOID; + } + + private static boolean allGettersPrefixed(List getters) { + return getters.stream().allMatch(getter -> !getterPrefix(getter.method()).isEmpty()); + } + + private static String getterPrefix(MethodTree getterMethod) { + String name = getterMethod.getName().toString(); + if (name.startsWith("get") && !name.equals("get")) { + return "get"; + } else if (name.startsWith("is") + && !name.equals("is") + && ASTHelpers.getType(getterMethod.getReturnType()).getKind() == TypeKind.BOOLEAN) { + return "is"; + } + return ""; + } + + /** Returns true if the getter and the setter are for the same field. */ + private static boolean matchGetterAndSetter( + MethodTree getter, MethodTree setter, boolean allGettersPrefixed) { + String getterName = getter.getName().toString(); + if (allGettersPrefixed) { + String prefix = getterPrefix(getter); + getterName = decapitalize(getterName.substring(prefix.length())); + } + String setterName = setter.getName().toString(); + return getterName.equals(setterName) + || setterName.equals( + "set" + Ascii.toUpperCase(getterName.charAt(0)) + getterName.substring(1)); + } + + /** + * Returns true if the method is a trivial factory method. + * + *

    A trivial factory method is a static method that has one argument for each getter in the + * class and contains a single return statement passing all the arguments to the constructor in + * the same order. + * + * @param methodTree The method tree to be checked. + * @param gettersCount The total number of getters in the class. + * @return True if the method is a trivial factory method, false otherwise. + */ + private static boolean isTrivialFactoryMethod(MethodTree methodTree, int gettersCount) { + var params = methodTree.getParameters(); + var statements = methodTree.getBody().getStatements(); + + // Trivial factory method must have one argument for each getter and a single return statement. + if (params.size() != gettersCount + || statements.size() != 1 + || !(statements.get(0) instanceof ReturnTree)) { + return false; + } + // Trivial factory method must return a new instance. + ReturnTree returnTree = (ReturnTree) statements.get(0); + if (!(returnTree.getExpression() instanceof NewClassTree)) { + return false; + } + // Trivial factory method must pass all the arguments to the constructor. + NewClassTree newClassTree = (NewClassTree) returnTree.getExpression(); + if (newClassTree.getArguments().stream().anyMatch(r -> !(r instanceof IdentifierTree))) { + return false; + } + // Compare the arguments passed to the method to those passed to the constructor. + var paramsNames = params.stream().map(p -> p.getName().toString()).collect(toImmutableList()); + var constructorArgs = + newClassTree.getArguments().stream() + .map(r -> ((IdentifierTree) r).getName().toString()) + .collect(toImmutableList()); + return paramsNames.equals(constructorArgs); + } + + /** + * Suggests a fix to replace the boxed type with the unboxed type. + * + *

    For example, if the type is `Integer`, the fix would be to replace `Integer` with `int`. + * + * @param tree The tree to be replaced, which can be either a return value or a parameter. + * @param state The visitor state. + * @param type The boxed type to be replaced. + */ + private static void suggestRemoveUnnecessaryBoxing( + Tree tree, VisitorState state, Type type, SuggestedFix.Builder fix) { + fix.replace(tree, unbox(state, type).tsym.getSimpleName().toString()); + } + + @AutoValue + abstract static class Getter { + abstract MethodTree method(); + + abstract SuggestedFix.Builder fix(); + + static Getter of(MethodTree method) { + return new AutoValue_AutoValueBoxedValues_Getter(method, SuggestedFix.builder()); + } + } +} diff --git a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java index e02825e352a..e6b874b486b 100644 --- a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java +++ b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java @@ -42,6 +42,7 @@ import com.google.errorprone.bugpatterns.AsyncCallableReturnsNull; import com.google.errorprone.bugpatterns.AsyncFunctionReturnsNull; import com.google.errorprone.bugpatterns.AttemptedNegativeZero; +import com.google.errorprone.bugpatterns.AutoValueBoxedValues; import com.google.errorprone.bugpatterns.AutoValueBuilderDefaultsInConstructor; import com.google.errorprone.bugpatterns.AutoValueFinalMethods; import com.google.errorprone.bugpatterns.AutoValueImmutableFields; @@ -1128,6 +1129,7 @@ public static ScannerSupplier warningChecks() { AssertFalse.class, AssistedInjectAndInjectOnConstructors.class, AutoFactoryAtInject.class, + AutoValueBoxedValues.class, AvoidObjectArrays.class, BanClassLoader.class, BanSerializableRead.class, diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java new file mode 100644 index 00000000000..392692d391f --- /dev/null +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java @@ -0,0 +1,590 @@ +/* + * Copyright 2024 The Error Prone Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.errorprone.bugpatterns; + +import static java.util.Arrays.stream; + +import com.google.auto.value.processor.AutoValueProcessor; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.CompilationTestHelper; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link AutoValueBoxedValues}. */ +@RunWith(TestParameterInjector.class) +public class AutoValueBoxedValuesTest { + private final CompilationTestHelper compilationHelper = + CompilationTestHelper.newInstance(AutoValueBoxedValues.class, getClass()) + .setArgs(ImmutableList.of("-processor", AutoValueProcessor.class.getName())); + ; + private final BugCheckerRefactoringTestHelper refactoringHelper = + BugCheckerRefactoringTestHelper.newInstance(AutoValueBoxedValues.class, getClass()) + .setArgs(ImmutableList.of("-processor", AutoValueProcessor.class.getName())); + + @TestParameter private boolean withBuilder; + + @Test + public void unnecessaryBoxedTypes_refactoring() { + refactoringHelper + .addInputLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract Long longId();", + " public abstract Integer intId();", + " public abstract Byte byteId();", + " public abstract Short shortId();", + " public abstract Float floatId();", + " public abstract Double doubleId();", + " public abstract Boolean booleanId();", + " public abstract Character charId();"), + linesWithoutBuilder( + " static Test create(", + " Long longId, Integer intId, Byte byteId, Short shortId,", + " Float floatId, Double doubleId, Boolean booleanId, Character charId) {", + " return new AutoValue_Test(longId, intId, byteId, shortId, floatId,", + " doubleId, booleanId, charId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " abstract Builder setIntId(Integer value);", + " abstract Builder setByteId(Byte value);", + " abstract Builder setShortId(Short value);", + " abstract Builder setFloatId(Float value);", + " abstract Builder setDoubleId(Double value);", + " abstract Builder setBooleanId(Boolean value);", + " abstract Builder setCharId(Character value);", + " abstract Test build();", + " }"), + lines("}"))) + .addOutputLines( + "out/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract long longId();", + " public abstract int intId();", + " public abstract byte byteId();", + " public abstract short shortId();", + " public abstract float floatId();", + " public abstract double doubleId();", + " public abstract boolean booleanId();", + " public abstract char charId();"), + linesWithoutBuilder( + " static Test create(", + " long longId, int intId, byte byteId, short shortId,", + " float floatId, double doubleId, boolean booleanId, char charId) {", + " return new AutoValue_Test(longId, intId, byteId, shortId, floatId,", + " doubleId, booleanId, charId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(long value);", + " abstract Builder setIntId(int value);", + " abstract Builder setByteId(byte value);", + " abstract Builder setShortId(short value);", + " abstract Builder setFloatId(float value);", + " abstract Builder setDoubleId(double value);", + " abstract Builder setBooleanId(boolean value);", + " abstract Builder setCharId(char value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void nullableBoxedTypes() { + compilationHelper + .addSourceLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "import javax.annotation.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract @Nullable Long longId();", + " public abstract @Nullable Integer intId();", + " public abstract @Nullable Byte byteId();", + " public abstract @Nullable Short shortId();", + " public abstract @Nullable Float floatId();", + " public abstract @Nullable Double doubleId();", + " public abstract @Nullable Boolean booleanId();", + " public abstract @Nullable Character charId();"), + linesWithoutBuilder( + " static Test create(", + " @Nullable Long longId, @Nullable Integer intId, @Nullable Byte byteId,", + " @Nullable Short shortId, @Nullable Float floatId,", + " @Nullable Double doubleId, @Nullable Boolean booleanId,", + " @Nullable Character charId) {", + " return new AutoValue_Test(longId, intId, byteId, shortId, floatId,", + " doubleId, booleanId, charId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(@Nullable Long value);", + " abstract Builder setIntId(@Nullable Integer value);", + " abstract Builder setByteId(@Nullable Byte value);", + " abstract Builder setShortId(@Nullable Short value);", + " abstract Builder setFloatId(@Nullable Float value);", + " abstract Builder setDoubleId(@Nullable Double value);", + " abstract Builder setBooleanId(@Nullable Boolean value);", + " abstract Builder setCharId(@Nullable Character value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void genericNullableBoxedTypes() { + compilationHelper + .addSourceLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "import org.checkerframework.checker.nullness.qual.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract @Nullable Long longId();", + " public abstract @Nullable Integer intId();", + " public abstract @Nullable Byte byteId();", + " public abstract @Nullable Short shortId();", + " public abstract @Nullable Float floatId();", + " public abstract @Nullable Double doubleId();", + " public abstract @Nullable Boolean booleanId();", + " public abstract @Nullable Character charId();"), + linesWithoutBuilder( + " static Test create(", + " @Nullable Long longId, @Nullable Integer intId, @Nullable Byte byteId,", + " @Nullable Short shortId, @Nullable Float floatId,", + " @Nullable Double doubleId, @Nullable Boolean booleanId,", + " @Nullable Character charId) {", + " return new AutoValue_Test(longId, intId, byteId, shortId, floatId,", + " doubleId, booleanId, charId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(@Nullable Long value);", + " abstract Builder setIntId(@Nullable Integer value);", + " abstract Builder setByteId(@Nullable Byte value);", + " abstract Builder setShortId(@Nullable Short value);", + " abstract Builder setFloatId(@Nullable Float value);", + " abstract Builder setDoubleId(@Nullable Double value);", + " abstract Builder setBooleanId(@Nullable Boolean value);", + " abstract Builder setCharId(@Nullable Character value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void primitiveTypes() { + compilationHelper + .addSourceLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract long longId();", + " public abstract int intId();", + " public abstract byte byteId();", + " public abstract short shortId();", + " public abstract float floatId();", + " public abstract double doubleId();", + " public abstract boolean booleanId();", + " public abstract char charId();"), + linesWithoutBuilder( + " static Test create(", + " long longId, int intId, byte byteId, short shortId,", + " float floatId, double doubleId, boolean booleanId, char charId) {", + " return new AutoValue_Test(longId, intId, byteId, shortId, floatId,", + " doubleId, booleanId, charId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(long value);", + " abstract Builder setIntId(int value);", + " abstract Builder setByteId(byte value);", + " abstract Builder setShortId(short value);", + " abstract Builder setFloatId(float value);", + " abstract Builder setDoubleId(double value);", + " abstract Builder setBooleanId(boolean value);", + " abstract Builder setCharId(char value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void nonBoxableTypes() { + compilationHelper + .addSourceLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "import javax.annotation.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract String stringId();", + " public @Nullable abstract String nullableStringId();"), + linesWithoutBuilder( + " static Test create(String stringId, @Nullable String nullableStringId) {", + " return new AutoValue_Test(stringId, nullableStringId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setStringId(String value);", + " abstract Builder setNullableStringId(@Nullable String value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void mixedTypes_refactoring() { + refactoringHelper + .addInputLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "import javax.annotation.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract @Nullable Long nullableId();", + " public abstract Long unnecessaryBoxedId();", + " public abstract long primitiveId();", + " public abstract String nonBoxableId();"), + linesWithoutBuilder( + " static Test create(", + " @Nullable Long nullableId, Long unnecessaryBoxedId,", + " long primitiveId, String nonBoxableId) {", + " return new AutoValue_Test(", + " nullableId, unnecessaryBoxedId, primitiveId, nonBoxableId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setNullableId(@Nullable Long value);", + " abstract Builder setUnnecessaryBoxedId(Long value);", + " abstract Builder setPrimitiveId(long value);", + " abstract Builder setNonBoxableId(String value);", + " abstract Test build();", + " }"), + lines("}"))) + .addOutputLines( + "out/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "import javax.annotation.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract @Nullable Long nullableId();", + " public abstract long unnecessaryBoxedId();", + " public abstract long primitiveId();", + " public abstract String nonBoxableId();"), + linesWithoutBuilder( + " static Test create(", + " @Nullable Long nullableId, long unnecessaryBoxedId,", + " long primitiveId, String nonBoxableId) {", + " return new AutoValue_Test(", + " nullableId, unnecessaryBoxedId, primitiveId, nonBoxableId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setNullableId(@Nullable Long value);", + " abstract Builder setUnnecessaryBoxedId(long value);", + " abstract Builder setPrimitiveId(long value);", + " abstract Builder setNonBoxableId(String value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void unnecessaryBoxedTypes_suppressWarnings() { + refactoringHelper + .addInputLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract Long longId();", + " @SuppressWarnings(\"AutoValueBoxedValues\")", + " public abstract Long longIdSuppressWarnings();"), + linesWithoutBuilder( + " static Test create(Long longId, Long longIdSuppressWarnings) {", + " return new AutoValue_Test(longId, longIdSuppressWarnings);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " @SuppressWarnings(\"AutoValueBoxedValues\")", + " abstract Builder setLongIdSuppressWarnings(Long value);", + " abstract Test build();", + " }"), + lines("}"))) + .addOutputLines( + "out/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract long longId();", + " @SuppressWarnings(\"AutoValueBoxedValues\")", + " public abstract Long longIdSuppressWarnings();"), + linesWithoutBuilder( + " static Test create(long longId, Long longIdSuppressWarnings) {", + " return new AutoValue_Test(longId, longIdSuppressWarnings);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(long value);", + " @SuppressWarnings(\"AutoValueBoxedValues\")", + " abstract Builder setLongIdSuppressWarnings(Long value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + + @Test + public void nullableGettersWithNonNullableSetters_noChange() { + if (!withBuilder) { + return; + } + compilationHelper + .addSourceLines( + "in/Test.java", + "import com.google.auto.value.AutoValue;", + "import javax.annotation.Nullable;", + "@AutoValue", + "abstract class Test {", + " public abstract @Nullable Long longId();", + " public abstract @Nullable Integer intId();", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " abstract Builder setIntId(Integer value);", + " abstract Test build();", + " }", + "}") + .doTest(); + } + + @Test + public void nonTrivialFactoryMethods_refectoring() { + if (withBuilder) { + return; + } + refactoringHelper + .addInputLines( + "in/Test.java", + "package test;", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " abstract int foo();", + " abstract Long bar();", + " static Test createTrivial(int foo, Long bar) {", + " return new AutoValue_Test(foo, bar);", + " }", + " static String notFactoryMethod(int foo, Long bar) {", + " return String.format(\"foo: %d, bar: %d\", foo, bar);", + " }", + " static Test createWrongOrder(Long bar, int foo) {", + " return new AutoValue_Test(foo, bar);", + " }", + " static Test createLessArguments(int foo) {", + " return new AutoValue_Test(foo, 0L);", + " }", + " static Test createMoreArguments(int foo, Long bar, Long baz) {", + " return new AutoValue_Test(foo, bar + baz);", + " }", + " static Test createWithValidation(int foo, Long bar) {", + " if (bar == null) { throw new AssertionError(); }", + " return new AutoValue_Test(foo, bar);", + " }", + " static Test createModifyArgs(int foo, Long bar) {", + " return new AutoValue_Test(foo + 1, bar);", + " }", + " static Test createModifyArgsIfNull(int foo, Long bar) {", + " return new AutoValue_Test(foo, bar == null ? 0L : bar);", + " }", + "}") + .addOutputLines( + "out/Test.java", + "package test;", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " abstract int foo();", + " abstract long bar();", + " static Test createTrivial(int foo, long bar) {", + " return new AutoValue_Test(foo, bar);", + " }", + " static String notFactoryMethod(int foo, Long bar) {", + " return String.format(\"foo: %d, bar: %d\", foo, bar);", + " }", + " static Test createWrongOrder(Long bar, int foo) {", + " return new AutoValue_Test(foo, bar);", + " }", + " static Test createLessArguments(int foo) {", + " return new AutoValue_Test(foo, 0L);", + " }", + " static Test createMoreArguments(int foo, Long bar, Long baz) {", + " return new AutoValue_Test(foo, bar + baz);", + " }", + " static Test createWithValidation(int foo, Long bar) {", + " if (bar == null) { throw new AssertionError(); }", + " return new AutoValue_Test(foo, bar);", + " }", + " static Test createModifyArgs(int foo, Long bar) {", + " return new AutoValue_Test(foo + 1, bar);", + " }", + " static Test createModifyArgsIfNull(int foo, Long bar) {", + " return new AutoValue_Test(foo, bar == null ? 0L : bar);", + " }", + "}") + .doTest(); + } + + @Test + public void settersWithoutSetPrefix() { + if (!withBuilder) { + return; + } + refactoringHelper + .addInputLines( + "in/Test.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract Long longId();", + " public abstract Boolean booleanId();", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder longId(Long value);", + " abstract Builder booleanId(Boolean value);", + " abstract Test build();", + " }", + "}") + .addOutputLines( + "out/Test.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract long longId();", + " public abstract boolean booleanId();", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder longId(long value);", + " abstract Builder booleanId(boolean value);", + " abstract Test build();", + " }", + "}") + .doTest(); + } + + @Test + public void allGettersWithPrefix() { + if (!withBuilder) { + return; + } + refactoringHelper + .addInputLines( + "in/Test.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract Long getLongId();", + " public abstract boolean isBooleanId();", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " abstract Builder setBooleanId(boolean value);", + " abstract Test build();", + " }", + "}") + .addOutputLines( + "out/Test.java", + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "abstract class Test {", + " public abstract long getLongId();", + " public abstract boolean isBooleanId();", + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(long value);", + " abstract Builder setBooleanId(boolean value);", + " abstract Test build();", + " }", + "}") + .doTest(); + } + + private static List lines(String... lines) { + return Arrays.asList(lines); + } + + private List linesWithBuilder(String... lines) { + return withBuilder ? Arrays.asList(lines) : new ArrayList<>(); + } + + private List linesWithoutBuilder(String... lines) { + return !withBuilder ? Arrays.asList(lines) : new ArrayList<>(); + } + + private static String[] mergeLines(List... blocks) { + return stream(blocks).flatMap(List::stream).toArray(String[]::new); + } +} diff --git a/docs/bugpattern/AutoValueBoxedValues.md b/docs/bugpattern/AutoValueBoxedValues.md new file mode 100644 index 00000000000..3c90729ad23 --- /dev/null +++ b/docs/bugpattern/AutoValueBoxedValues.md @@ -0,0 +1,11 @@ +AutoValue classes reject `null` values, unless the property is annotated with +`@Nullable`. For this reason, the usage of boxed primitives (e.g. `Long`) is +discouraged, except when annotated as `@Nullable`. Otherwise they can be +replaced with the corresponding primitive. There could be some cases where the +usage of a boxed primitive might be intentional to avoid boxing the value again +after invoking the getter. + +## Suppression + +Suppress violations by using `@SuppressWarnings("AutoValueBoxedValues")` on the +relevant `abstract` getter and/or setter. From d88730767480d5c9ac190c4960f783e79c22c457 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Thu, 1 Aug 2024 16:30:08 -0700 Subject: [PATCH 31/39] Omit some unnecessary break statements when translating to `->` switches PiperOrigin-RevId: 658578239 --- .../StatementSwitchToExpressionSwitch.java | 14 +++---- ...StatementSwitchToExpressionSwitchTest.java | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index cf3a3ca05c7..39b5b7585d0 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -36,7 +36,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Streams; import com.google.errorprone.BugPattern; import com.google.errorprone.ErrorProneFlags; @@ -585,7 +584,7 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( groupedCaseCommentsAccumulator.length() == 0 ? null : groupedCaseCommentsAccumulator.toString(), - transformedBlockSource.isEmpty() ? null : transformedBlockSource, + transformedBlockSource.isEmpty() ? null : transformedBlockSource.trim(), commentsBeforeRemovedBreak.orElse(null), commentsAfterCaseOptional.orElse(null)); } @@ -902,7 +901,7 @@ && getStatements(caseTree).size() > filteredStatements.size()) { state .getSourceCode() .subSequence( - state.getEndPosition(Iterables.getLast(filteredStatements)), + state.getEndPosition(getLast(filteredStatements)), getStartPosition(getStatements(caseTree).get(getStatements(caseTree).size() - 1))) .toString() .trim(); @@ -952,11 +951,10 @@ private static String transformBlock( StringBuilder transformedBlockBuilder = new StringBuilder(); int codeBlockStart = extractLhsComments(caseTree, state, transformedBlockBuilder); - int codeBlockEnd = - filteredStatements.isEmpty() - ? getBlockEnd(state, caseTree) - : state.getEndPosition(Streams.findLast(filteredStatements.stream()).get()); - transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); + if (!filteredStatements.isEmpty()) { + int codeBlockEnd = state.getEndPosition(getLast(filteredStatements)); + transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); + } return transformedBlockBuilder.toString(); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index eab2d16735b..be63a77f336 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -361,7 +361,6 @@ public void switchByEnumCard2_removesRedundantBreaks_error() { " // Post break comment", " case DIAMOND -> {", " // Diamond break comment", - " break;", " }", " case SPADE, CLUB -> System.out.println(\"everything else\");", " }", @@ -1385,7 +1384,6 @@ public void switchByEnum_surroundingBracesCannotRemove_error() { " switch(side) {", " case OBVERSE -> {", " // The quick brown fox, jumps over the lazy dog, etc.", - " break;", " }", " default -> ", " throw new RuntimeException(\"Invalid type.\");", @@ -3623,4 +3621,40 @@ public void i4222() { .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true") .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } + + @Test + public void unnecessaryBreaks() { + assumeTrue(RuntimeVersion.isAtLeast14()); + refactoringHelper + .addInputLines( + "Test.java", + "public class Test {", + " public static void main(String[] args) {", + " switch (args.length) {", + " case 0:", + " System.out.println(0);", + " break;", + " default:", + " // hello", + " // world", + " break;", + " }", + " }", + "}") + .addOutputLines( + "Test.java", + "public class Test {", + " public static void main(String[] args) {", + " switch (args.length) {", + " case 0 -> System.out.println(0);", + " default -> {", + " // hello", + " // world", + " }", + " }", + " }", + "}") + .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } } From ccd3ca657b2bf41224eccb1633d94046d09ede82 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Thu, 1 Aug 2024 16:57:40 -0700 Subject: [PATCH 32/39] Add handling of toBuilder() toBuilder() was identified as a getter leading to not correctly identifying the case where all the getters are prefixed. PiperOrigin-RevId: 658585727 --- .../bugpatterns/AutoValueBoxedValues.java | 68 +++++++++++-------- .../bugpatterns/AutoValueBoxedValuesTest.java | 4 +- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java index 6b57f5f6989..7a8c240d0f4 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java @@ -21,6 +21,9 @@ import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.Matchers.hasModifier; import static com.google.errorprone.util.ASTHelpers.getSymbol; +import static com.google.errorprone.util.ASTHelpers.getType; +import static com.google.errorprone.util.ASTHelpers.hasAnnotation; +import static com.google.errorprone.util.ASTHelpers.isSameType; import static java.beans.Introspector.decapitalize; import com.google.auto.value.AutoValue; @@ -34,7 +37,6 @@ import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; -import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ClassTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MethodTree; @@ -60,31 +62,24 @@ public class AutoValueBoxedValues extends BugChecker implements ClassTreeMatcher @Override public Description matchClass(ClassTree tree, VisitorState state) { - if (!ASTHelpers.hasAnnotation(tree, AutoValue.class.getName(), state)) { + if (!hasAnnotation(tree, AutoValue.class.getName(), state)) { return NO_MATCH; } + Optional builderClass = findBuilderClass(tree, state); + // Identify and potentially fix the getters. - ImmutableList getters = handleGetterMethods(tree, state); + ImmutableList getters = handleGetterMethods(tree, state, builderClass); // If we haven't modified any getter, it's ok to stop. if (getters.stream().allMatch(getter -> getter.fix().isEmpty())) { return NO_MATCH; } - // Handle the Builder class, if there is one. - boolean builderFound = false; - for (Tree memberTree : tree.getMembers()) { - if (memberTree instanceof ClassTree - && ASTHelpers.hasAnnotation(memberTree, AutoValue.Builder.class.getName(), state)) { - handleSetterMethods((ClassTree) memberTree, state, getters); - builderFound = true; - break; - } - } - - // If a builder was not found, handle the factory methods. - if (!builderFound) { + // Handle the Builder class, if there is one. Otherwise handle the factory methods. + if (builderClass.isPresent()) { + handleSetterMethods(builderClass.get(), state, getters); + } else { handleFactoryMethods(tree, state, getters); } @@ -103,13 +98,16 @@ public Description matchClass(ClassTree tree, VisitorState state) { * @param state The visitor state. * @return The list of {@link Getter} in the class. */ - private ImmutableList handleGetterMethods(ClassTree classTree, VisitorState state) { + private ImmutableList handleGetterMethods( + ClassTree classTree, VisitorState state, Optional builderClass) { return classTree.getMembers().stream() .filter(MethodTree.class::isInstance) .map(memberTree -> (MethodTree) memberTree) .filter( methodTree -> - ABSTRACT_MATCHER.matches(methodTree, state) && methodTree.getParameters().isEmpty()) + ABSTRACT_MATCHER.matches(methodTree, state) + && methodTree.getParameters().isEmpty() + && !isToBuilderMethod(methodTree, state, builderClass)) .map(methodTree -> maybeFixGetter(methodTree, state)) .collect(toImmutableList()); } @@ -120,7 +118,7 @@ private ImmutableList handleGetterMethods(ClassTree classTree, VisitorSt */ private Getter maybeFixGetter(MethodTree method, VisitorState state) { Getter getter = Getter.of(method); - Type type = ASTHelpers.getType(method.getReturnType()); + Type type = getType(method.getReturnType()); if (!isSuppressed(method, state) && !hasNullableAnnotation(method) && isBoxedPrimitive(state, type)) { @@ -143,7 +141,8 @@ private void handleSetterMethods(ClassTree classTree, VisitorState state, List ABSTRACT_MATCHER.matches(methodTree, state) - && methodTree.getParameters().size() == 1) + && methodTree.getParameters().size() == 1 + && isSameType(getType(methodTree.getReturnType()), getType(classTree), state)) .forEach(methodTree -> maybeFixSetter(methodTree, state, getters)); } @@ -162,7 +161,7 @@ && matchGetterAndSetter(getter.method(), methodTree, allGettersPrefixed)) .findAny(); if (fixedGetter.isPresent()) { var parameter = methodTree.getParameters().get(0); - Type type = ASTHelpers.getType(parameter); + Type type = getType(parameter); if (isBoxedPrimitive(state, type) && !hasNullableAnnotation(parameter)) { suggestRemoveUnnecessaryBoxing(parameter.getType(), state, type, fixedGetter.get().fix()); } @@ -188,10 +187,8 @@ private void handleFactoryMethods(ClassTree classTree, VisitorState state, List< .filter( methodTree -> STATIC_MATCHER.matches(methodTree, state) - && ASTHelpers.isSameType( - ASTHelpers.getType(methodTree.getReturnType()), - ASTHelpers.getType(classTree), - state) + && isSameType( + getType(methodTree.getReturnType()), getType(classTree), state) && isTrivialFactoryMethod(methodTree, getters.size())) .findAny(); if (trivialFactoryMethod.isEmpty()) { @@ -201,7 +198,7 @@ && isTrivialFactoryMethod(methodTree, getters.size())) Getter getter = getters.get(idx); if (!getter.fix().isEmpty()) { var parameter = trivialFactoryMethod.get().getParameters().get(idx); - Type type = ASTHelpers.getType(parameter); + Type type = getType(parameter); if (isBoxedPrimitive(state, type) && !hasNullableAnnotation(parameter)) { suggestRemoveUnnecessaryBoxing(parameter.getType(), state, type, getter.fix()); } @@ -228,6 +225,23 @@ private static boolean isBoxedPrimitive(VisitorState state, Type type) { return unboxed != null && unboxed.getTag() != TypeTag.NONE && unboxed.getTag() != TypeTag.VOID; } + private static Optional findBuilderClass(ClassTree tree, VisitorState state) { + return tree.getMembers().stream() + .filter( + memberTree -> + memberTree instanceof ClassTree + && hasAnnotation(memberTree, AutoValue.Builder.class.getName(), state)) + .map(memberTree -> (ClassTree) memberTree) + .findAny(); + } + + private static boolean isToBuilderMethod( + MethodTree methodTree, VisitorState state, Optional builderClass) { + return builderClass.isPresent() + && !STATIC_MATCHER.matches(methodTree, state) + && isSameType(getType(methodTree.getReturnType()), getType(builderClass.get()), state); + } + private static boolean allGettersPrefixed(List getters) { return getters.stream().allMatch(getter -> !getterPrefix(getter.method()).isEmpty()); } @@ -238,7 +252,7 @@ private static String getterPrefix(MethodTree getterMethod) { return "get"; } else if (name.startsWith("is") && !name.equals("is") - && ASTHelpers.getType(getterMethod.getReturnType()).getKind() == TypeKind.BOOLEAN) { + && getType(getterMethod.getReturnType()).getKind() == TypeKind.BOOLEAN) { return "is"; } return ""; diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java index 392692d391f..93a0b1fa96b 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java @@ -536,7 +536,7 @@ public void settersWithoutSetPrefix() { } @Test - public void allGettersWithPrefix() { + public void allGettersWithPrefix_ignoreToBuilder() { if (!withBuilder) { return; } @@ -548,6 +548,7 @@ public void allGettersWithPrefix() { "abstract class Test {", " public abstract Long getLongId();", " public abstract boolean isBooleanId();", + " public abstract Builder toBuilder();", " @AutoValue.Builder", " abstract static class Builder {", " abstract Builder setLongId(Long value);", @@ -562,6 +563,7 @@ public void allGettersWithPrefix() { "abstract class Test {", " public abstract long getLongId();", " public abstract boolean isBooleanId();", + " public abstract Builder toBuilder();", " @AutoValue.Builder", " abstract static class Builder {", " abstract Builder setLongId(long value);", From ac7ebf5a03043abd4af00c9f47a6ba1a46a5e4f0 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 2 Aug 2024 08:46:35 -0700 Subject: [PATCH 33/39] Handle `var` in MustBeClosedChecker Fixes https://github.com/google/error-prone/issues/4164 PiperOrigin-RevId: 658802144 --- .../AbstractMustBeClosedChecker.java | 23 ++++++++-- .../bugpatterns/MustBeClosedCheckerTest.java | 43 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java index 96306a48d29..d6ed6678c41 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AbstractMustBeClosedChecker.java @@ -16,6 +16,7 @@ package com.google.errorprone.bugpatterns; +import static com.google.errorprone.fixes.SuggestedFixes.prettyType; import static com.google.errorprone.fixes.SuggestedFixes.qualifyType; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.Matchers.instanceMethod; @@ -28,6 +29,7 @@ import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.getType; import static com.google.errorprone.util.ASTHelpers.hasAnnotation; +import static com.google.errorprone.util.ASTHelpers.hasExplicitSource; import static com.google.errorprone.util.ASTHelpers.isConsideredFinal; import static com.google.errorprone.util.ASTHelpers.isSameType; import static com.google.errorprone.util.ASTHelpers.isSubtype; @@ -467,16 +469,29 @@ private static Optional extractToResourceInCurrentTry( private static Optional splitVariableDeclarationAroundTry( ExpressionTree tree, VariableTree var, VisitorState state, NameSuggester suggester) { int initPos = getStartPosition(var.getInitializer()); - int afterTypePos = state.getEndPosition(var.getType()); + Tree type = var.getType(); + String typePrefix; + int startPos; + if (hasExplicitSource(type, state)) { + startPos = state.getEndPosition(type); + typePrefix = ""; + } else { + startPos = getStartPosition(var); + typePrefix = prettyType(getType(type), state); + } String name = suggester.suggestName(tree); return Change.builder( SuggestedFix.builder() .replace( - afterTypePos, + startPos, initPos, String.format( - " %s;\ntry (var %s = %s) {\n%s =", - var.getName(), name, state.getSourceForNode(tree), var.getName())) + "%s %s;\ntry (var %s = %s) {\n%s =", + typePrefix, + var.getName(), + name, + state.getSourceForNode(tree), + var.getName())) .replace(tree, name) .build()) .closeBraceAfter(var) diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/MustBeClosedCheckerTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/MustBeClosedCheckerTest.java index d4b48d6bad0..cb67d7eaa92 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/MustBeClosedCheckerTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/MustBeClosedCheckerTest.java @@ -155,4 +155,47 @@ public void forLoopUnfixable() { .expectUnchanged() .doTest(); } + + @Test + public void localVariableTypeInference() { + refactoringHelper + .addInputLines( + "Closeable.java", + "class Closeable implements AutoCloseable {", + " @Override", + " public void close() {}", + " public int method() {", + " return 1;", + " }", + " }") + .expectUnchanged() + .addInputLines( + "Foo.java", + "import com.google.errorprone.annotations.MustBeClosed;", + "class Foo {", + " @MustBeClosed", + " Closeable mustBeClosedMethod() {", + " return null;", + " }", + "}") + .expectUnchanged() + .addInputLines( + "Test.java", + "class Test {", + " void test(Foo foo) {", + " var bar = foo.mustBeClosedMethod().method();", + " }", + "}") + .addOutputLines( + "Test.java", + "class Test {", + " void test(Foo foo) {", + " int bar;", + " try (var closeable = foo.mustBeClosedMethod()) {", + " bar = closeable.method();", + " }", + " }", + "}") + .doTest(); + } } From 474554a79ae02f6f1482b2e3da78e88c11db37ff Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 2 Aug 2024 10:16:59 -0700 Subject: [PATCH 34/39] Remove `// fall out` comments, which are sometimes used to document an empty `default:` statement group PiperOrigin-RevId: 658827224 --- .../StatementSwitchToExpressionSwitch.java | 2 +- ...StatementSwitchToExpressionSwitchTest.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index 39b5b7585d0..824600c1015 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -94,7 +94,7 @@ public final class StatementSwitchToExpressionSwitch extends BugChecker ImmutableSet.of(THROW, EXPRESSION_STATEMENT); private static final ImmutableSet KINDS_RETURN_OR_THROW = ImmutableSet.of(THROW, RETURN); private static final Pattern FALL_THROUGH_PATTERN = - Pattern.compile("\\bfalls?.?through\\b", Pattern.CASE_INSENSITIVE); + Pattern.compile("\\bfalls?.?(through|out)\\b", Pattern.CASE_INSENSITIVE); // Default (negative) result for assignment switch conversion analysis. Note that the value is // immutable. private static final AssignmentSwitchAnalysisResult DEFAULT_ASSIGNMENT_SWITCH_ANALYSIS_RESULT = diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index be63a77f336..0ea13ecd6a1 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -3657,4 +3657,36 @@ public void unnecessaryBreaks() { .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true") .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } + + @Test + public void fallOutComment() { + assumeTrue(RuntimeVersion.isAtLeast14()); + refactoringHelper + .addInputLines( + "Test.java", + "public class Test {", + " void f(int x) {", + " switch (x) {", + " case 0:", + " System.err.println(\"ZERO\");", + " break;", + " default:", + " // fall out", + " }", + " }", + "}") + .addOutputLines( + "Test.java", + "public class Test {", + " void f(int x) {", + " switch (x) {", + " case 0 -> System.err.println(\"ZERO\");", + " default -> {}", + " }", + " }", + "}") + .setArgs( + ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } } From 86df5cf166a68ca99c8a045932ca70196e0a6773 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 2 Aug 2024 11:05:57 -0700 Subject: [PATCH 35/39] Convert some simple blocks to return switches using `yield` PiperOrigin-RevId: 658843015 --- .../StatementSwitchToExpressionSwitch.java | 48 ++++++++++++------- ...StatementSwitchToExpressionSwitchTest.java | 41 +++++++++++----- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index 824600c1015..ce273dc7587 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -94,7 +94,7 @@ public final class StatementSwitchToExpressionSwitch extends BugChecker ImmutableSet.of(THROW, EXPRESSION_STATEMENT); private static final ImmutableSet KINDS_RETURN_OR_THROW = ImmutableSet.of(THROW, RETURN); private static final Pattern FALL_THROUGH_PATTERN = - Pattern.compile("\\bfalls?.?(through|out)\\b", Pattern.CASE_INSENSITIVE); + Pattern.compile("\\bfalls?.?through\\b", Pattern.CASE_INSENSITIVE); // Default (negative) result for assignment switch conversion analysis. Note that the value is // immutable. private static final AssignmentSwitchAnalysisResult DEFAULT_ASSIGNMENT_SWITCH_ANALYSIS_RESULT = @@ -325,14 +325,21 @@ private static CaseQualifications analyzeCaseForReturnSwitch( return previousCaseQualifications; } - // Statement blocks on the RHS are not currently supported - if (!(statements.size() == 1 && KINDS_RETURN_OR_THROW.contains(statements.get(0).getKind()))) { + // Statement blocks on the RHS are not currently supported, except for trivial blocks of + // statements expressions followed by a return or throw + // TODO: handle more complex statement blocks that can be converted using 'yield' + if (statements.isEmpty()) { + return CaseQualifications.SOME_OR_ALL_CASES_DONT_QUALIFY; + } + StatementTree lastStatement = getLast(statements); + if (!statements.subList(0, statements.size() - 1).stream() + .allMatch(statement -> statement.getKind().equals(EXPRESSION_STATEMENT)) + || !KINDS_RETURN_OR_THROW.contains(lastStatement.getKind())) { return CaseQualifications.SOME_OR_ALL_CASES_DONT_QUALIFY; } - StatementTree onlyStatement = statements.get(0); // For this analysis, cases that don't return something can be disregarded - if (!onlyStatement.getKind().equals(RETURN)) { + if (!lastStatement.getKind().equals(RETURN)) { return previousCaseQualifications; } @@ -343,7 +350,7 @@ private static CaseQualifications analyzeCaseForReturnSwitch( } // This is the first value-returning case that we are examining - Type returnType = ASTHelpers.getType(((ReturnTree) onlyStatement).getExpression()); + Type returnType = ASTHelpers.getType(((ReturnTree) lastStatement).getExpression()); return returnType == null // Return of void does not qualify ? CaseQualifications.SOME_OR_ALL_CASES_DONT_QUALIFY @@ -1117,21 +1124,30 @@ private static String transformReturnOrThrowBlock( CaseTree caseTree, VisitorState state, List statements) { StringBuilder transformedBlockBuilder = new StringBuilder(); - int codeBlockStart; - int codeBlockEnd = - statements.isEmpty() - ? getBlockEnd(state, caseTree) - : state.getEndPosition(Streams.findLast(statements.stream()).get()); - - if (statements.size() == 1 && statements.get(0).getKind().equals(RETURN)) { + int codeBlockEnd = state.getEndPosition(caseTree); + if (statements.size() > 1) { + transformedBlockBuilder.append("{\n"); + int codeBlockStart = extractLhsComments(caseTree, state, transformedBlockBuilder); + int offset = transformedBlockBuilder.length(); + transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); + transformedBlockBuilder.append("\n}"); + ReturnTree returnTree = (ReturnTree) getLast(statements); + int start = getStartPosition(returnTree); + transformedBlockBuilder.replace( + offset + start - codeBlockStart, + offset + start - codeBlockStart + "return".length(), + "yield"); + } else if (statements.size() == 1 && statements.get(0).getKind().equals(RETURN)) { // For "return x;", we want to take source starting after the "return" int unused = extractLhsComments(caseTree, state, transformedBlockBuilder); ReturnTree returnTree = (ReturnTree) statements.get(0); - codeBlockStart = getStartPosition(returnTree.getExpression()); + int codeBlockStart = getStartPosition(returnTree.getExpression()); + codeBlockEnd = state.getEndPosition(Streams.findLast(statements.stream()).get()); + transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); } else { - codeBlockStart = extractLhsComments(caseTree, state, transformedBlockBuilder); + int codeBlockStart = extractLhsComments(caseTree, state, transformedBlockBuilder); + transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); } - transformedBlockBuilder.append(state.getSourceCode(), codeBlockStart, codeBlockEnd); return transformedBlockBuilder.toString(); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index 0ea13ecd6a1..461ae792822 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -3659,34 +3659,53 @@ public void unnecessaryBreaks() { } @Test - public void fallOutComment() { + public void mixedExpressionsAndYields() { assumeTrue(RuntimeVersion.isAtLeast14()); refactoringHelper .addInputLines( "Test.java", "public class Test {", - " void f(int x) {", + " String f(int x) {", " switch (x) {", " case 0:", - " System.err.println(\"ZERO\");", - " break;", + " return \"ZERO\";", + " case 1:", + " return \"ONE\";", + " case 2: // hello", + " // world", + " System.err.println();", + " System.err.println();", + " return \"TWO\";", + " // hello", + " // world", " default:", - " // fall out", + " return \"\";", " }", " }", "}") .addOutputLines( "Test.java", "public class Test {", - " void f(int x) {", - " switch (x) {", - " case 0 -> System.err.println(\"ZERO\");", - " default -> {}", - " }", + " String f(int x) {", + " return switch (x) {", + " case 0 -> \"ZERO\";", + " case 1 -> \"ONE\";", + " case 2 -> {", + " // hello", + " // world", + " System.err.println();", + " System.err.println();", + " yield \"TWO\";", + " }", + " // hello", + " // world", + " default -> \"\";", + " };", " }", "}") .setArgs( - ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) + "-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true", + "-XepOpt:StatementSwitchToExpressionSwitch:EnableReturnSwitchConversion=true") .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); } } From a706e8d800abcafd6bfeb48c21bfd953261b0519 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Tue, 6 Aug 2024 11:21:37 -0700 Subject: [PATCH 36/39] Add ability to suppress warning for the entire AutoValue class PiperOrigin-RevId: 660029479 --- .../bugpatterns/AutoValueBoxedValues.java | 2 +- .../bugpatterns/AutoValueBoxedValuesTest.java | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java index 7a8c240d0f4..06025ebd325 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java @@ -62,7 +62,7 @@ public class AutoValueBoxedValues extends BugChecker implements ClassTreeMatcher @Override public Description matchClass(ClassTree tree, VisitorState state) { - if (!hasAnnotation(tree, AutoValue.class.getName(), state)) { + if (!hasAnnotation(tree, AutoValue.class.getName(), state) || isSuppressed(tree, state)) { return NO_MATCH; } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java index 93a0b1fa96b..a7c6cc92aa9 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java @@ -345,6 +345,34 @@ public void mixedTypes_refactoring() { .doTest(); } + @Test + public void unnecessaryBoxedTypes_suppressWarningsForClass() { + compilationHelper + .addSourceLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "@AutoValue", + "@SuppressWarnings(\"AutoValueBoxedValues\")", + "abstract class Test {", + " public abstract Long longId();", + " public abstract Integer intId();"), + linesWithoutBuilder( + " static Test create(Long longId, Integer intId) {", + " return new AutoValue_Test(longId, intId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " abstract Builder setIntId(Integer value);", + " abstract Test build();", + " }"), + lines("}"))) + .doTest(); + } + @Test public void unnecessaryBoxedTypes_suppressWarnings() { refactoringHelper From ba8f9a285f542c8b628cf50e365bd0270b0aac45 Mon Sep 17 00:00:00 2001 From: Error Prone Team Date: Tue, 6 Aug 2024 12:10:10 -0700 Subject: [PATCH 37/39] Do not update getters that override methods from a superclass. The change would be incorrect as the modified method is no longer matching the signature of the method in the superclass. PiperOrigin-RevId: 660049434 --- .../bugpatterns/AutoValueBoxedValues.java | 7 +++ .../bugpatterns/AutoValueBoxedValuesTest.java | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java index 06025ebd325..c47831af89d 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/AutoValueBoxedValues.java @@ -20,6 +20,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.Matchers.hasModifier; +import static com.google.errorprone.util.ASTHelpers.findSuperMethods; import static com.google.errorprone.util.ASTHelpers.getSymbol; import static com.google.errorprone.util.ASTHelpers.getType; import static com.google.errorprone.util.ASTHelpers.hasAnnotation; @@ -121,6 +122,7 @@ private Getter maybeFixGetter(MethodTree method, VisitorState state) { Type type = getType(method.getReturnType()); if (!isSuppressed(method, state) && !hasNullableAnnotation(method) + && !isOverride(method, state) && isBoxedPrimitive(state, type)) { suggestRemoveUnnecessaryBoxing(method.getReturnType(), state, type, getter.fix()); } @@ -211,6 +213,11 @@ private static boolean hasNullableAnnotation(Tree tree) { return NullnessAnnotations.fromAnnotationsOn(getSymbol(tree)).orElse(null) == Nullness.NULLABLE; } + /** Returns true if the method overrides another method. */ + private static boolean isOverride(MethodTree methodTree, VisitorState state) { + return !findSuperMethods(getSymbol(methodTree), state.getTypes()).isEmpty(); + } + /** Returns the primitive type corresponding to a boxed type. */ private static Type unbox(VisitorState state, Type type) { return state.getTypes().unboxedType(type); diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java index a7c6cc92aa9..adc536f4801 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/AutoValueBoxedValuesTest.java @@ -425,6 +425,64 @@ public void unnecessaryBoxedTypes_suppressWarnings() { .doTest(); } + @Test + public void unnecessaryBoxedTypes_overrides() { + refactoringHelper + .addInputLines( + "in/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "class Test {", + " interface SuperClass {", + " Long superClassLongId();", + " }", + " @AutoValue", + " static abstract class BaseClass implements SuperClass {", + " public abstract Long longId();", + " @Override", + " public abstract Long superClassLongId();"), + linesWithoutBuilder( + " static BaseClass create(Long longId, Long superClassLongId) {", + " return new AutoValue_Test_BaseClass(longId, superClassLongId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(Long value);", + " abstract Builder setSuperClassLongId(Long value);", + " abstract BaseClass build();", + " }"), + lines(" }", "}"))) + .addOutputLines( + "out/Test.java", + mergeLines( + lines( + "import com.google.auto.value.AutoValue;", + "class Test {", + " interface SuperClass {", + " Long superClassLongId();", + " }", + " @AutoValue", + " static abstract class BaseClass implements SuperClass {", + " public abstract long longId();", + " @Override", + " public abstract Long superClassLongId();"), + linesWithoutBuilder( + " static BaseClass create(long longId, Long superClassLongId) {", + " return new AutoValue_Test_BaseClass(longId, superClassLongId);", + " }"), + linesWithBuilder( + " @AutoValue.Builder", + " abstract static class Builder {", + " abstract Builder setLongId(long value);", + " abstract Builder setSuperClassLongId(Long value);", + " abstract BaseClass build();", + " }"), + lines(" }", "}"))) + .doTest(); + } + @Test public void nullableGettersWithNonNullableSetters_noChange() { if (!withBuilder) { From af175b07510ef093b5e76200608d15d3190bbd17 Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Thu, 8 Aug 2024 07:20:10 -0700 Subject: [PATCH 38/39] Don't fire the `CanIgnoreReturnValueSuggester` for `dagger.producers.ProductionComponent.Builder` and `dagger.producers.ProductionSubcomponent.Builder`. #checkreturnvalue PiperOrigin-RevId: 660826447 --- .../checkreturnvalue/CanIgnoreReturnValueSuggester.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java index 15fd049c4cd..72d0a4ec697 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java @@ -86,10 +86,15 @@ public final class CanIgnoreReturnValueSuggester extends BugChecker implements M private static final ImmutableSet EXEMPTING_CLASS_ANNOTATIONS = ImmutableSet.of( - "com.google.auto.value.AutoValue.Builder", + // keep-sorted start "com.google.auto.value.AutoBuilder", + "com.google.auto.value.AutoValue.Builder", "dagger.Component.Builder", - "dagger.Subcomponent.Builder"); + "dagger.Subcomponent.Builder", + "dagger.producers.ProductionComponent.Builder", + "dagger.producers.ProductionSubcomponent.Builder" + // keep-sorted end + ); private static final Supplier PROTO_BUILDER = VisitorState.memoize(s -> s.getTypeFromString("com.google.protobuf.MessageLite.Builder")); From 5ada179028452868623a1aa8f11eee2996886454 Mon Sep 17 00:00:00 2001 From: cushon Date: Sat, 10 Aug 2024 18:34:06 +0000 Subject: [PATCH 39/39] Release Error Prone 2.30.0 --- annotation/pom.xml | 2 +- annotations/pom.xml | 2 +- check_api/pom.xml | 2 +- core/pom.xml | 2 +- docgen/pom.xml | 2 +- docgen_processor/pom.xml | 2 +- pom.xml | 2 +- refaster/pom.xml | 2 +- test_helpers/pom.xml | 2 +- type_annotations/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/annotation/pom.xml b/annotation/pom.xml index fbbba7c5524..00d1f37d8b9 100644 --- a/annotation/pom.xml +++ b/annotation/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 @BugPattern annotation diff --git a/annotations/pom.xml b/annotations/pom.xml index 62ebfe88a40..fc4510bbd09 100644 --- a/annotations/pom.xml +++ b/annotations/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 error-prone annotations diff --git a/check_api/pom.xml b/check_api/pom.xml index 21c7a05fcd5..e7ab3873c53 100644 --- a/check_api/pom.xml +++ b/check_api/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 error-prone check api diff --git a/core/pom.xml b/core/pom.xml index 41f23273342..b64a9a11bcb 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 error-prone library diff --git a/docgen/pom.xml b/docgen/pom.xml index 8ee00587502..3f99646fbf0 100644 --- a/docgen/pom.xml +++ b/docgen/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 Documentation tool for generating Error Prone bugpattern documentation diff --git a/docgen_processor/pom.xml b/docgen_processor/pom.xml index 7d97a4cb4ca..60567033901 100644 --- a/docgen_processor/pom.xml +++ b/docgen_processor/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 JSR-269 annotation processor for @BugPattern annotation diff --git a/pom.xml b/pom.xml index 1a2615bb86d..a62be1ff26f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ Error Prone parent POM com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 pom Error Prone is a static analysis tool for Java that catches common programming mistakes at compile-time. diff --git a/refaster/pom.xml b/refaster/pom.xml index 968b0c2bae4..f92081d525f 100644 --- a/refaster/pom.xml +++ b/refaster/pom.xml @@ -19,7 +19,7 @@ error_prone_parent com.google.errorprone - 1.0-HEAD-SNAPSHOT + 2.30.0 4.0.0 diff --git a/test_helpers/pom.xml b/test_helpers/pom.xml index 66b12d308c3..2dfeb461fd4 100644 --- a/test_helpers/pom.xml +++ b/test_helpers/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 error-prone test helpers diff --git a/type_annotations/pom.xml b/type_annotations/pom.xml index 365123af188..b23f541611c 100644 --- a/type_annotations/pom.xml +++ b/type_annotations/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.30.0 error-prone type annotations

  • JLS * §5.6.1 */ - @Nullable - private static Type unaryNumericPromotion(Type type, VisitorState state) { + private static @Nullable Type unaryNumericPromotion(Type type, VisitorState state) { Type unboxed = unboxAndEnsureNumeric(type, state); switch (unboxed.getTag()) { case BYTE: @@ -1758,8 +1734,8 @@ private static Type unaryNumericPromotion(Type type, VisitorState state) { *