Skip to content

Commit 080b01e

Browse files
authored
Add new ReferenceComparison check (#11)
1 parent 01bb1a7 commit 080b01e

4 files changed

Lines changed: 270 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2022 ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package tech.pegasys.tools.epchecks;
14+
15+
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
16+
import static com.google.errorprone.matchers.Description.NO_MATCH;
17+
import static com.sun.source.tree.Tree.Kind.EQUAL_TO;
18+
import static com.sun.source.tree.Tree.Kind.IDENTIFIER;
19+
import static com.sun.source.tree.Tree.Kind.NOT_EQUAL_TO;
20+
import static com.sun.source.tree.Tree.Kind.NULL_LITERAL;
21+
22+
import com.google.auto.service.AutoService;
23+
import com.google.errorprone.BugPattern;
24+
import com.google.errorprone.VisitorState;
25+
import com.google.errorprone.bugpatterns.BugChecker;
26+
import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
27+
import com.google.errorprone.matchers.Description;
28+
import com.google.errorprone.matchers.Matcher;
29+
import com.google.errorprone.matchers.Matchers;
30+
import com.google.errorprone.util.ASTHelpers;
31+
import com.sun.source.tree.BinaryTree;
32+
import com.sun.source.tree.IdentifierTree;
33+
import com.sun.source.tree.Tree;
34+
import com.sun.tools.javac.code.Type;
35+
36+
/**
37+
* This check is very similar to the <a
38+
* href=http://errorprone.info/bugpattern/ReferenceEquality>ReferenceEquality</a> check in Error
39+
* Prone. But this one identifies <code>Object</code> comparisons for types without an explicitly
40+
* declared <code>equals()</code> method.
41+
*/
42+
@AutoService(BugChecker.class)
43+
@BugPattern(
44+
name = "ReferenceComparison",
45+
summary = "Reference comparison should be value comparison.",
46+
severity = SUGGESTION,
47+
linkType = BugPattern.LinkType.NONE)
48+
public class ReferenceComparison extends BugChecker implements BinaryTreeMatcher {
49+
private static final Matcher<BinaryTree> REFERENCE_COMPARISON =
50+
Matchers.anyOf(Matchers.kindIs(EQUAL_TO), Matchers.kindIs(NOT_EQUAL_TO));
51+
private static final Matcher<Tree> NULL = Matchers.kindIs(NULL_LITERAL);
52+
private static final Matcher<Tree> ENUM = Matchers.isSubtypeOf("java.lang.Enum");
53+
private static final Matcher<Tree> CLASS = Matchers.isSubtypeOf("java.lang.Class");
54+
55+
@Override
56+
public Description matchBinary(BinaryTree tree, VisitorState state) {
57+
// We're looking for reference comparisons (== and !=).
58+
if (!REFERENCE_COMPARISON.matches(tree, state)) {
59+
return NO_MATCH;
60+
}
61+
62+
final Tree left = tree.getLeftOperand();
63+
final Tree right = tree.getRightOperand();
64+
final Type leftType = ASTHelpers.getType(left);
65+
final Type rightType = ASTHelpers.getType(right);
66+
67+
if (leftType == null || rightType == null) {
68+
return NO_MATCH;
69+
}
70+
71+
// When a boxed type is compared to a primitive, it'll be unboxed.
72+
if (leftType.isPrimitive() || rightType.isPrimitive()) {
73+
return NO_MATCH;
74+
}
75+
76+
// Ignore comparisons with "this" identifier.
77+
if (isThis(left) || isThis(right)) {
78+
return NO_MATCH;
79+
}
80+
81+
// Ignore null comparisons.
82+
if (NULL.matches(left, state) || NULL.matches(right, state)) {
83+
return NO_MATCH;
84+
}
85+
86+
// Ignore reference comparisons with enums. Those are a special case.
87+
if (ENUM.matches(left, state) || ENUM.matches(right, state)) {
88+
return NO_MATCH;
89+
}
90+
91+
// Ignore class comparisons, those are generally fine.
92+
if (CLASS.matches(left, state) || CLASS.matches(right, state)) {
93+
return NO_MATCH;
94+
}
95+
96+
return describeMatch(tree);
97+
}
98+
99+
private static boolean isThis(Tree tree) {
100+
if (tree.getKind().equals(IDENTIFIER)) {
101+
final IdentifierTree identifier = (IdentifierTree) tree;
102+
return identifier.getName().contentEquals("this");
103+
}
104+
return false;
105+
}
106+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2022 ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package tech.pegasys.tools.epchecks;
14+
15+
import com.google.errorprone.CompilationTestHelper;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.Test;
18+
19+
public class ReferenceComparisonTest {
20+
21+
private CompilationTestHelper compilationHelper;
22+
23+
@BeforeEach
24+
public void setup() {
25+
compilationHelper = CompilationTestHelper.newInstance(ReferenceComparison.class, getClass());
26+
}
27+
28+
@Test
29+
public void referenceComparisonPositiveCases() {
30+
compilationHelper.addSourceFile("ReferenceComparisonPositiveCases.java").doTest();
31+
}
32+
33+
@Test
34+
public void referenceComparisonNegativeCases() {
35+
compilationHelper.addSourceFile("ReferenceComparisonNegativeCases.java").doTest();
36+
}
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
16+
package tech.pegasys.tools.epchecks;
17+
18+
import com.google.common.primitives.Bytes;
19+
import org.junit.Test;
20+
21+
public class ReferenceComparisonNegativeCases {
22+
public boolean comparisonWithEquals(final Bytes a, final Bytes b) {
23+
return a.equals(b);
24+
}
25+
26+
public boolean comparisonWithEnums() {
27+
return TestEnum.A == TestEnum.B;
28+
}
29+
30+
public boolean comparisonWithThis() {
31+
Object o = null;
32+
return this == o;
33+
}
34+
35+
public boolean comparisonWithNull(final Bytes a) {
36+
return a == null;
37+
}
38+
39+
public boolean comparisonBoxedPrimitiveAndPrimitive(final Integer a) {
40+
return a == 27;
41+
}
42+
43+
public boolean comparisonWithGetClass(final Bytes a, final Bytes b) {
44+
return a.getClass() == b.getClass();
45+
}
46+
47+
public boolean comparisonWithEnumVariable(final TestEnum a) {
48+
return a == TestEnum.B;
49+
}
50+
51+
public boolean comparisonWithEnumVariables(final TestEnum a, final TestEnum b) {
52+
return a == b;
53+
}
54+
55+
private enum TestEnum {
56+
A,
57+
B,
58+
C;
59+
}
60+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
16+
package tech.pegasys.tools.epchecks;
17+
18+
import java.util.List;
19+
20+
public class ReferenceComparisonPositiveCases {
21+
public void comparisonWithIdentifiers(final List<Integer> a, final List<Integer> b) {
22+
// BUG: Diagnostic contains: Reference comparison should be value comparison
23+
if (a == b) {
24+
return;
25+
}
26+
// BUG: Diagnostic contains: Reference comparison should be value comparison
27+
if (a != b) {
28+
return;
29+
}
30+
// BUG: Diagnostic contains: Reference comparison should be value comparison
31+
if (b == a) {
32+
return;
33+
}
34+
// BUG: Diagnostic contains: Reference comparison should be value comparison
35+
if (b != a) {
36+
return;
37+
}
38+
}
39+
40+
public boolean comparisonReturn(final List<Integer> a, final List<Integer> b) {
41+
// BUG: Diagnostic contains: Reference comparison should be value comparison
42+
return a == b;
43+
}
44+
45+
public boolean comparisonReturnWithOr(final List<Integer> a, final List<Integer> b) {
46+
// BUG: Diagnostic contains: Reference comparison should be value comparison
47+
return false || a == b;
48+
}
49+
50+
public void comparisonWithIdentifierAndMethodInvocationr(final List<Integer> b) {
51+
// BUG: Diagnostic contains: Reference comparison should be value comparison
52+
if (List.of(1, 2, 3) == b) {
53+
return;
54+
}
55+
// BUG: Diagnostic contains: Reference comparison should be value comparison
56+
if (b == List.of(1, 2, 3)) {
57+
return;
58+
}
59+
}
60+
61+
public void comparisonWithMethodInvocations(final List<Integer> b) {
62+
// BUG: Diagnostic contains: Reference comparison should be value comparison
63+
if (List.of(1, 2, 3) == List.of(1, 2, 3, 4)) {
64+
return;
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)