Skip to content

Commit bc9618a

Browse files
markhbradyError Prone Team
authored andcommitted
[IfChainToSwitch] when safe mode is enabled, don't reorder cases because this can change program semantics. Update docs accordingly.
PiperOrigin-RevId: 865428565
1 parent 6cbafd3 commit bc9618a

3 files changed

Lines changed: 44 additions & 63 deletions

File tree

core/src/main/java/com/google/errorprone/bugpatterns/IfChainToSwitch.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,8 @@ private static boolean switchOnNullWouldImplicitlyThrow(
422422
* Analyzes the supplied case IRs for a switch statement for issues related default/unconditional
423423
* cases. If deemed necessary, this method injects a `default` and/or `case null` into the
424424
* supplied case IRs. If the supplied case IRs cannot be used to form a syntactically valid switch
425-
* statement, returns `Optional.empty()`.
425+
* statement, returns `Optional.empty()`. Precondition: the list of supplied cases must not
426+
* contain any dominance violations.
426427
*/
427428
private Optional<List<CaseIr>> maybeFixDefaultNullAndUnconditional(
428429
List<CaseIr> cases,
@@ -608,7 +609,11 @@ && isSubtype(
608609
}
609610
}
610611

611-
return recheckDominanceNeeded ? maybeFixDominance(cases, state, subject) : Optional.of(cases);
612+
return recheckDominanceNeeded
613+
// If there is a dominance violation, then we must have caused it by adding a case, so
614+
// fixing it merely puts the new case in the correct position
615+
? maybeFixDominance(cases, state, subject, /* canReorderCases= */ true)
616+
: Optional.of(cases);
612617
}
613618

614619
/**
@@ -705,12 +710,13 @@ private static Optional<List<CaseIr>> maybeDetectDuplicateConstants(List<CaseIr>
705710
}
706711

707712
/**
708-
* Analyzes the supplied case IRs for dominance violations. If a dominance violation is detected,
709-
* attempts to fix the problem by reordering them so that the violation does not occur. If the
710-
* dominance violation cannot be corrected by this algorithm, returns {@code Optional.empty()}.
713+
* Analyzes the supplied case IRs for dominance violations. If a dominance violation is detected
714+
* and {@code canReorderCases} is {@code true}, attempts to fix the problem by reordering cases so
715+
* that no violation is present. If a dominance violation exists and cannot be corrected by this
716+
* algorithm, returns {@code Optional.empty()}.
711717
*/
712718
private static Optional<List<CaseIr>> maybeFixDominance(
713-
List<CaseIr> cases, VisitorState state, ExpressionTree subject) {
719+
List<CaseIr> cases, VisitorState state, ExpressionTree subject, boolean canReorderCases) {
714720

715721
List<CaseIr> casesCopy = new ArrayList<>(cases);
716722
List<CaseIr> casesToInsert = new ArrayList<>();
@@ -720,8 +726,12 @@ private static Optional<List<CaseIr>> maybeFixDominance(
720726
// they were encountered in the if-chain)
721727
casesToInsert.add(casesCopy.get(insert));
722728

729+
// Violation found when trying to insert @insert?
723730
if (hasDominanceViolation(casesToInsert, state, subject)) {
724-
// Violation found when trying to insert @insert
731+
if (!canReorderCases) {
732+
// Dominance violation exists and not allowed to fix
733+
return Optional.empty();
734+
}
725735
casesToInsert.remove(casesToInsert.size() - 1);
726736

727737
// Slow path: try to clean up by moving insertion around. The idea is a bit like an
@@ -1380,7 +1390,9 @@ private List<SuggestedFix> deepAnalysisOfIfChain(
13801390
Optional<List<CaseIr>> fixedCasesOptional =
13811391
casesWithPullupMaybeApplied
13821392
.flatMap(caseList -> maybeDetectDuplicateConstants(caseList))
1383-
.flatMap(caseList -> maybeFixDominance(caseList, state, subject))
1393+
.flatMap(
1394+
caseList ->
1395+
maybeFixDominance(caseList, state, subject, /* canReorderCases= */ !enableSafe))
13841396
.flatMap(
13851397
x ->
13861398
maybeFixDefaultNullAndUnconditional(
@@ -1402,7 +1414,10 @@ private List<SuggestedFix> deepAnalysisOfIfChain(
14021414
fixedCasesOptional =
14031415
Optional.of(cases)
14041416
.flatMap(caseList -> maybeDetectDuplicateConstants(caseList))
1405-
.flatMap(caseList -> maybeFixDominance(caseList, state, subject))
1417+
.flatMap(
1418+
caseList ->
1419+
maybeFixDominance(
1420+
caseList, state, subject, /* canReorderCases= */ !enableSafe))
14061421
.flatMap(
14071422
caseList ->
14081423
maybeFixDefaultNullAndUnconditional(
@@ -1416,7 +1431,10 @@ private List<SuggestedFix> deepAnalysisOfIfChain(
14161431
switchType,
14171432
ifTreeSourceRange))
14181433
// Changing default/null can affect dominance
1419-
.flatMap(caseList -> maybeFixDominance(caseList, state, subject));
1434+
.flatMap(
1435+
caseList ->
1436+
maybeFixDominance(
1437+
caseList, state, subject, /* canReorderCases= */ !enableSafe));
14201438
}
14211439

14221440
List<SuggestedFix> suggestedFixes = new ArrayList<>();

core/src/test/java/com/google/errorprone/bugpatterns/IfChainToSwitchTest.java

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,11 +1211,10 @@ public void foo(Suit s) {
12111211
}
12121212

12131213
@Test
1214-
public void ifChain_legalDuplicateSafe_error() {
1215-
// Although the guard effectively duplicates the diamond constant case, this construction is
1216-
// legal
1217-
refactoringHelper
1218-
.addInputLines(
1214+
public void ifChain_legalDuplicateSafe_noError() {
1215+
// Cannot reorder cases to fix dominance when safe mode is enabled
1216+
helper
1217+
.addSourceLines(
12191218
"Test.java",
12201219
"""
12211220
import java.lang.Number;
@@ -1234,26 +1233,8 @@ public void foo(Suit s) {
12341233
}
12351234
}
12361235
""")
1237-
.addOutputLines(
1238-
"Test.java",
1239-
"""
1240-
import java.lang.Number;
1241-
1242-
class Test {
1243-
public void foo(Suit s) {
1244-
switch (s) {
1245-
case Suit.DIAMOND -> System.out.println("Diamond");
1246-
case Suit.HEART -> System.out.println("Heart");
1247-
case Suit ss when ss == Suit.DIAMOND -> System.out.println("Technically allowed");
1248-
case Suit r -> System.out.println("It's some black suit");
1249-
case null -> {}
1250-
}
1251-
}
1252-
}
1253-
""")
12541236
.setArgs("-XepOpt:IfChainToSwitch:EnableMain", "-XepOpt:IfChainToSwitch:EnableSafe")
1255-
.setFixChooser(IfChainToSwitchTest::assertOneFixAndChoose)
1256-
.doTest(TEXT_MATCH);
1237+
.doTest();
12571238
}
12581239

12591240
@Test
@@ -2659,9 +2640,10 @@ public void foo(Suit s) {
26592640
}
26602641

26612642
@Test
2662-
public void ifChain_domination1Safe_error() {
2663-
refactoringHelper
2664-
.addInputLines(
2643+
public void ifChain_domination1Safe_noError() {
2644+
// Domination violation, but safe mode enabled, so no error
2645+
helper
2646+
.addSourceLines(
26652647
"Test.java",
26662648
"""
26672649
import java.lang.Number;
@@ -2684,29 +2666,8 @@ public void foo(Suit s) {
26842666
}
26852667
}
26862668
""")
2687-
.addOutputLines(
2688-
"Test.java",
2689-
"""
2690-
import java.lang.Number;
2691-
2692-
class Test {
2693-
public void foo(Suit s) {
2694-
Object suit = s;
2695-
System.out.println("yo");
2696-
switch (suit) {
2697-
case String unused -> System.out.println("It's a string!");
2698-
case Suit unused when suit == Suit.DIAMOND -> System.out.println("It's a Suit!");
2699-
case Suit suity when suit == Suit.SPADE -> System.out.println("It's a Suity!");
2700-
case Suit unused -> System.out.println("It's a diamond!");
2701-
case Number unused -> System.out.println("It's a number!");
2702-
case null, default -> throw new AssertionError();
2703-
}
2704-
}
2705-
}
2706-
""")
27072669
.setArgs("-XepOpt:IfChainToSwitch:EnableMain", "-XepOpt:IfChainToSwitch:EnableSafe")
2708-
.setFixChooser(IfChainToSwitchTest::assertOneFixAndChoose)
2709-
.doTest(TEXT_MATCH);
2670+
.doTest();
27102671
}
27112672

27122673
@Test

docs/bugpattern/IfChainToSwitch.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ Note that with the new `switch` style (`->`), one gets exhaustiveness checking
5858
the `switch` would raise a compile-time error, whereas the original chain of
5959
`if` statements would need to be manually detected and edited.
6060

61-
If the flag `EnableSafe` is set, the output will include an empty `case null`;
62-
this more closely matches the behavior of the original if-chain when `suit` is
63-
`null`, although is more verbose and may not match the intent.
61+
If the flag `-XepOpt:IfChainToSwitch:EnableSafe=true` is set, the output will
62+
include an empty `case null`; this more closely matches the behavior of the
63+
original if-chain when `suit` is `null`, although is more verbose and may not
64+
match the intent.
6465

6566
``` {.good}
6667
enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};
@@ -149,4 +150,5 @@ private void describeObject(Object obj) {
149150
```
150151

151152
In this way, the behavior of the switch (`It's a string!`) and the original
152-
if-chain (`It's an object!`) are different.
153+
if-chain (`It's an object!`) are different. To prevent the checker from changing
154+
behavior in this way, set the flag `-XepOpt:IfChainToSwitch:EnableSafe=true`.

0 commit comments

Comments
 (0)