Skip to content

Commit b8ab713

Browse files
markhbradyError Prone Team
authored andcommitted
[IfChainToSwitch] add unit tests for handling of duplicated patterns
PiperOrigin-RevId: 875389833
1 parent 29b03d0 commit b8ab713

File tree

4 files changed

+344
-32
lines changed

4 files changed

+344
-32
lines changed

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

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
import static com.google.common.collect.ImmutableSet.toImmutableSet;
2222
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2323
import static com.google.errorprone.bugpatterns.SwitchUtils.COMPILE_TIME_CONSTANT_MATCHER;
24+
import static com.google.errorprone.bugpatterns.SwitchUtils.getReferencedLocalVariablesInTree;
2425
import static com.google.errorprone.bugpatterns.SwitchUtils.isEnumValue;
2526
import static com.google.errorprone.bugpatterns.SwitchUtils.renderComments;
2627
import static com.google.errorprone.matchers.Description.NO_MATCH;
2728
import static com.google.errorprone.util.ASTHelpers.constValue;
2829
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
2930
import static com.google.errorprone.util.ASTHelpers.getType;
31+
import static com.google.errorprone.util.ASTHelpers.isConsideredFinal;
3032
import static com.google.errorprone.util.ASTHelpers.isSubtype;
3133
import static com.google.errorprone.util.ASTHelpers.sameVariable;
34+
import static com.google.errorprone.util.ASTHelpers.stripParentheses;
3235
import static com.sun.source.tree.Tree.Kind.EXPRESSION_STATEMENT;
3336
import static com.sun.source.tree.Tree.Kind.THROW;
3437
import static java.lang.Math.max;
@@ -416,6 +419,28 @@ private static boolean switchOnNullWouldImplicitlyThrow(
416419
&& cases.stream().noneMatch(CaseIr::hasCaseNull);
417420
}
418421

422+
/**
423+
* Determines whether the given case IR has an "unguarded" pattern. As defined in the JLS,
424+
* "unguarded" means that either there is no guard or the guard is the boolean literal `true`.
425+
*/
426+
private static boolean isUnguarded(CaseIr caseIr) {
427+
// Not a pattern
428+
if (caseIr.instanceOfOptional().isEmpty()) {
429+
return false;
430+
}
431+
432+
if (caseIr.guardOptional().isEmpty()) {
433+
return true;
434+
}
435+
436+
// Guard is present and is `true`
437+
ExpressionTree guard = stripParentheses(caseIr.guardOptional().get());
438+
return isBooleanLiteral(guard)
439+
&& guard instanceof LiteralTree literalTree
440+
&& literalTree.getValue() instanceof Boolean b
441+
&& b;
442+
}
443+
419444
/**
420445
* Analyzes the supplied case IRs for a switch statement for issues related default/unconditional
421446
* cases. If deemed necessary, this method injects a `default` and/or `case null` into the
@@ -450,7 +475,7 @@ private Optional<List<CaseIr>> maybeFixDefaultNullAndUnconditional(
450475
.filter(
451476
caseIr ->
452477
caseIr.instanceOfOptional().isPresent()
453-
&& caseIr.guardOptional().isEmpty()
478+
&& isUnguarded(caseIr)
454479
&& isSubtype(
455480
getType(subject),
456481
getType(caseIr.instanceOfOptional().get().type()),
@@ -674,12 +699,13 @@ && switchOnNullWouldImplicitlyThrow(
674699
}
675700

676701
/**
677-
* Analyzes the supplied case IRs for duplicate constants (either primitives or enum values). If
702+
* Analyzes the supplied case IRs for duplicate constants (primitives, enum values, or `null`). If
678703
* any duplicates are found, returns {@code Optional.empty()}.
679704
*/
680705
private static Optional<List<CaseIr>> maybeDetectDuplicateConstants(List<CaseIr> cases) {
681706

682707
Set<Object> seenConstants = new HashSet<>();
708+
boolean seenNull = false;
683709

684710
for (CaseIr caseIr : cases) {
685711
if (caseIr.expressionsOptional().isPresent()) {
@@ -701,6 +727,13 @@ private static Optional<List<CaseIr>> maybeDetectDuplicateConstants(List<CaseIr>
701727
}
702728
seenConstants.add(sym);
703729
}
730+
731+
if (isNull(expression)) {
732+
if (seenNull) {
733+
return Optional.empty();
734+
}
735+
seenNull = true;
736+
}
704737
}
705738
}
706739
}
@@ -987,6 +1020,12 @@ private Optional<ExpressionTree> validatePredicateForSubject(
9871020
&& !b) {
9881021
return Optional.empty();
9891022
}
1023+
// A guard cannot reference a local variable that is not final nor effectively final;
1024+
// see JLS 21 §14.11.1.
1025+
if (getReferencedLocalVariablesInTree(rightOperandNoParentheses).stream()
1026+
.anyMatch(varSymbol -> !isConsideredFinal(varSymbol))) {
1027+
return Optional.empty();
1028+
}
9901029
// Update last case to attach the guard
9911030
cases.set(
9921031
currentCasesSize,
@@ -1657,7 +1696,7 @@ public static boolean isDominatedBy(
16571696
boolean isPrimitive = getType(constantExpression).isPrimitive();
16581697
if (isPrimitive) {
16591698
// Guarded patterns cannot dominate primitives
1660-
if (lhs.guardOptional().isPresent()) {
1699+
if (!isUnguarded(lhs)) {
16611700
continue;
16621701
}
16631702
if (lhs.instanceOfOptional().isPresent()) {
@@ -1689,7 +1728,7 @@ public static boolean isDominatedBy(
16891728
}
16901729
boolean isEnum = isEnumValue(constantExpression, state);
16911730
if (isEnum) {
1692-
if (lhs.guardOptional().isPresent()) {
1731+
if (!isUnguarded(lhs)) {
16931732
// Guarded patterns cannot dominate enum values
16941733
continue;
16951734
}
@@ -1710,7 +1749,7 @@ public static boolean isDominatedBy(
17101749
// RHS must be a reference
17111750
// The rhs-reference code would be needed to support e.g. String literals. It is included
17121751
// for completeness.
1713-
if (lhs.guardOptional().isPresent()) {
1752+
if (!isUnguarded(lhs)) {
17141753
// Guarded patterns cannot dominate references
17151754
continue;
17161755
}
@@ -1741,7 +1780,7 @@ public static boolean isDominatedBy(
17411780
}
17421781

17431782
// RHS must be a pattern
1744-
if (lhs.guardOptional().isPresent()) {
1783+
if (!isUnguarded(lhs)) {
17451784
// LHS has a guard, so cannot dominate RHS
17461785
return false;
17471786
}

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

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.common.collect.Iterables.getLast;
2222
import static com.google.common.collect.Iterables.getOnlyElement;
2323
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
24+
import static com.google.errorprone.bugpatterns.SwitchUtils.getReferencedLocalVariablesInTree;
2425
import static com.google.errorprone.matchers.Description.NO_MATCH;
2526
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
2627
import static com.google.errorprone.util.ASTHelpers.getSymbol;
@@ -557,28 +558,7 @@ private void handle(Tree tree) {
557558
* supplied {@code tree}.
558559
*/
559560
private static boolean hasReadsOrWritesOfVariableInTree(VarSymbol symbol, Tree tree) {
560-
Set<VarSymbol> referencedLocalVariables = new HashSet<>();
561-
new TreeScanner<Void, Void>() {
562-
@Override
563-
public Void visitMemberSelect(MemberSelectTree memberSelect, Void unused) {
564-
handle(memberSelect);
565-
return super.visitMemberSelect(memberSelect, null);
566-
}
567-
568-
@Override
569-
public Void visitIdentifier(IdentifierTree identifier, Void unused) {
570-
handle(identifier);
571-
return super.visitIdentifier(identifier, null);
572-
}
573-
574-
private void handle(Tree tree) {
575-
var symbol = getSymbol(tree);
576-
if (symbol instanceof VarSymbol varSymbol) {
577-
referencedLocalVariables.add(varSymbol);
578-
}
579-
}
580-
}.scan(tree, null);
581-
return referencedLocalVariables.contains(symbol);
561+
return getReferencedLocalVariablesInTree(tree).contains(symbol);
582562
}
583563

584564
/**

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,5 +442,30 @@ static String renderNullDefaultKindPrefix(
442442
};
443443
}
444444

445+
public static Set<VarSymbol> getReferencedLocalVariablesInTree(Tree tree) {
446+
Set<VarSymbol> referencedLocalVariables = new HashSet<>();
447+
new TreeScanner<Void, Void>() {
448+
@Override
449+
public Void visitMemberSelect(MemberSelectTree memberSelect, Void unused) {
450+
handle(memberSelect);
451+
return super.visitMemberSelect(memberSelect, null);
452+
}
453+
454+
@Override
455+
public Void visitIdentifier(IdentifierTree identifier, Void unused) {
456+
handle(identifier);
457+
return super.visitIdentifier(identifier, null);
458+
}
459+
460+
private void handle(Tree tree) {
461+
var symbol = getSymbol(tree);
462+
if (symbol instanceof VarSymbol varSymbol) {
463+
referencedLocalVariables.add(varSymbol);
464+
}
465+
}
466+
}.scan(tree, null);
467+
return referencedLocalVariables;
468+
}
469+
445470
private SwitchUtils() {}
446471
}

0 commit comments

Comments
 (0)