Skip to content

Commit 56f3b7c

Browse files
committed
Add wrong-Comparator-nullsFirst-nullsLast-usage-for-nullable-key.ql
1 parent 95c027b commit 56f3b7c

File tree

1 file changed

+67
-0
lines changed

1 file changed

+67
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Finds incorrect usage of the `Comparator` factory methods `nullsFirst` and `nullsLast` where the
3+
* desired behavior is apparently to handle nullable 'sort keys'.
4+
*
5+
* For example, consider this class:
6+
* ```java
7+
* class MyClass {
8+
* public Integer getI() {
9+
* return 2;
10+
* }
11+
*
12+
* public String getNullableS() {
13+
* return null;
14+
* }
15+
* }
16+
* ```
17+
*
18+
* An incorrect usage of `nullsFirst` might look like this:
19+
* ```java
20+
* Comparator<MyClass> comp = Comparator.comparing(MyClass::getI)
21+
* .thenComparing(Comparator.nullsFirst(Comparator.comparing(MyClass::getNullableS)));
22+
* ```
23+
*
24+
* The problem is that `nullsFirst` here applies to the compared `MyClass`, not the nullable `String`.
25+
* So this will cause a `NullPointerException`.
26+
*
27+
* Instead one of the factory methods with separate 'sort key extractor' should be used, for example:
28+
* ```java
29+
* Comparator<MyClass> comp = Comparator.comparing(MyClass::getI)
30+
* // First extracts the 'sort key', and then applies `nullsFirst` on it
31+
* .thenComparing(MyClass::getNullableS, Comparator.nullsFirst(Comparator.naturalOrder()));
32+
* ```
33+
*
34+
* @id TODO
35+
* @kind problem
36+
*/
37+
38+
import java
39+
40+
class ComparatorMethod extends Method {
41+
ComparatorMethod() {
42+
getSourceDeclaration().getDeclaringType().hasQualifiedName("java.util", "Comparator")
43+
}
44+
}
45+
46+
from MethodAccess nullsHandlingCall, ComparatorMethod nullsHandlingMethod
47+
where
48+
// Check for cases where `nullsFirst` / `nullsLast` is part of a comparator chain, and therefore the
49+
// intention was likely to handle null for a 'key' and not the object itself (otherwise the complete
50+
// chain would have been wrapped with `nulls...`)
51+
(
52+
// `comp.thenComparing(nulls)`
53+
exists(MethodAccess thenComparingCall |
54+
thenComparingCall.getMethod().(ComparatorMethod).getSignature() =
55+
"thenComparing(java.util.Comparator)" and
56+
thenComparingCall.getArgument(0) = nullsHandlingCall
57+
)
58+
or
59+
// `nulls.thenComparing(...)`
60+
exists(MethodAccess thenComparingCall |
61+
thenComparingCall.getMethod().(ComparatorMethod).getName().matches("thenComparing%") and
62+
thenComparingCall.getQualifier() = nullsHandlingCall
63+
)
64+
) and
65+
nullsHandlingMethod = nullsHandlingCall.getMethod() and
66+
nullsHandlingMethod.hasName(["nullsFirst", "nullsLast"])
67+
select nullsHandlingCall, "Potential incorrect usage of `" + nullsHandlingMethod.getName() + "`"

0 commit comments

Comments
 (0)