Skip to content

Commit 06d8d2d

Browse files
committed
Add simplifiable-duration-constant.ql
1 parent 2ec4f18 commit 06d8d2d

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/**
2+
* Finds duration constants which can be simplified to increase readability.
3+
*
4+
* For example `Duration.ofSeconds(3600)` could be written as `Duration.ofHours(1)` instead.
5+
*
6+
* This query was inspired by [this Guava commit](https://github.com/google/guava/commit/9ea68165fcb78aace43a7b80b0a0e77c7327789b).
7+
*
8+
* @kind problem
9+
* @id todo
10+
*/
11+
12+
import java
13+
14+
newtype TDurationUnit =
15+
TDurationNanos() or
16+
TDurationMicros() or
17+
TDurationMillis() or
18+
TDurationSeconds() or
19+
TDurationMinutes() or
20+
TDurationHours() or
21+
TDurationDays()
22+
23+
abstract class DurationUnit extends TDurationUnit {
24+
/** Gets the name of the corresponding `java.util.concurrent.TimeUnit` constant. */
25+
abstract string getTimeUnitName();
26+
27+
/** Gets the name of the corresponding `java.time.Duration` method, if any. */
28+
abstract string getDurationMethodName();
29+
30+
string toString() { result = getTimeUnitName() }
31+
32+
/** Gets the next higher unit, if any. */
33+
abstract DurationUnit getHigherUnit();
34+
35+
/** Gets the multiplication factor to the next higher unit, if any. */
36+
abstract int getHigherUnitFactor();
37+
38+
int getFactorTo(DurationUnit higherUnit) {
39+
if higherUnit = this
40+
then result = 1
41+
else result = getHigherUnitFactor() * getHigherUnit().getFactorTo(higherUnit)
42+
}
43+
}
44+
45+
class DurationNanos extends DurationUnit, TDurationNanos {
46+
override string getTimeUnitName() { result = "NANOSECONDS" }
47+
48+
override string getDurationMethodName() { result = "ofNanos" }
49+
50+
override DurationUnit getHigherUnit() { result instanceof DurationMicros }
51+
52+
override int getHigherUnitFactor() { result = 1000 }
53+
}
54+
55+
class DurationMicros extends DurationUnit, TDurationMicros {
56+
override string getTimeUnitName() { result = "MICROSECONDS" }
57+
58+
override string getDurationMethodName() {
59+
// Has no `Duration` method
60+
none()
61+
}
62+
63+
override DurationUnit getHigherUnit() { result instanceof DurationMillis }
64+
65+
override int getHigherUnitFactor() { result = 1000 }
66+
}
67+
68+
class DurationMillis extends DurationUnit, TDurationMillis {
69+
override string getTimeUnitName() { result = "MILLISECONDS" }
70+
71+
override string getDurationMethodName() { result = "ofMillis" }
72+
73+
override DurationUnit getHigherUnit() { result instanceof DurationSeconds }
74+
75+
override int getHigherUnitFactor() { result = 1000 }
76+
}
77+
78+
class DurationSeconds extends DurationUnit, TDurationSeconds {
79+
override string getTimeUnitName() { result = "SECONDS" }
80+
81+
override string getDurationMethodName() { result = "ofSeconds" }
82+
83+
override DurationUnit getHigherUnit() { result instanceof DurationMinutes }
84+
85+
override int getHigherUnitFactor() { result = 60 }
86+
}
87+
88+
class DurationMinutes extends DurationUnit, TDurationMinutes {
89+
override string getTimeUnitName() { result = "MINUTES" }
90+
91+
override string getDurationMethodName() { result = "ofMinutes" }
92+
93+
override DurationUnit getHigherUnit() { result instanceof DurationHours }
94+
95+
override int getHigherUnitFactor() { result = 60 }
96+
}
97+
98+
class DurationHours extends DurationUnit, TDurationHours {
99+
override string getTimeUnitName() { result = "HOURS" }
100+
101+
override string getDurationMethodName() { result = "ofHours" }
102+
103+
override DurationUnit getHigherUnit() { result instanceof DurationDays }
104+
105+
override int getHigherUnitFactor() { result = 24 }
106+
}
107+
108+
class DurationDays extends DurationUnit, TDurationDays {
109+
override string getTimeUnitName() { result = "DAYS" }
110+
111+
override string getDurationMethodName() { result = "ofDays" }
112+
113+
override DurationUnit getHigherUnit() { none() }
114+
115+
override int getHigherUnitFactor() { none() }
116+
}
117+
118+
bindingset[value, divisor]
119+
int divExact(int value, int divisor) {
120+
result = value / divisor and
121+
value % divisor = 0
122+
}
123+
124+
bindingset[valueIn]
125+
predicate simplifyDuration(DurationUnit unitIn, int valueIn, DurationUnit unitOut, int valueOut) {
126+
unitOut = unitIn.getHigherUnit+() and
127+
valueOut = divExact(valueIn, unitIn.getFactorTo(unitOut))
128+
}
129+
130+
class TypeTimeUnit extends Class {
131+
TypeTimeUnit() { hasQualifiedName("java.util.concurrent", "TimeUnit") }
132+
}
133+
134+
bindingset[declaringType, callableName, valueParamIndex, timeUnitParamIndex]
135+
predicate isTimeUnitParam(
136+
string declaringType, string callableName, int valueParamIndex, int timeUnitParamIndex
137+
) {
138+
exists(string s |
139+
s =
140+
[
141+
"java.util.concurrent.TimeUnit,convert,0,1", "java.lang.Process,waitFor,0,1",
142+
"java.nio.channels.AsynchronousChannelGroup,awaitTermination,0,1",
143+
"java.nio.channels.AsynchronousSocketChannel,read,1,2",
144+
"java.nio.channels.AsynchronousSocketChannel,read,3,4",
145+
"java.nio.channels.AsynchronousSocketChannel,write,1,2",
146+
"java.nio.channels.AsynchronousSocketChannel,write,3,4",
147+
"java.nio.file.WatchService,poll,0,1",
148+
"java.nio.file.attribute.FileTime,from,0,1",
149+
"javax.swing.SwingWorker,get,0,1",
150+
]
151+
|
152+
declaringType = s.splitAt(",", 0) and
153+
callableName = s.splitAt(",", 1) and
154+
valueParamIndex = s.splitAt(",", 2).toInt() and
155+
timeUnitParamIndex = s.splitAt(",", 3).toInt()
156+
)
157+
or
158+
// For simplicity don't list them all individually (maybe have to do that if this here is too errorprone)
159+
declaringType.matches("java.util.concurrent.%") and
160+
valueParamIndex + 1 = timeUnitParamIndex and
161+
// Ignore methods where time unit applies to multiple time values (and therefore would have to check
162+
// if all of the values can be simplified)
163+
not callableName = ["scheduleAtFixedRate", "scheduleWithFixedDelay"]
164+
}
165+
166+
Callable getAnOverridden(Callable callable) {
167+
result = callable.getSourceDeclaration() or
168+
callable.(Method).getASourceOverriddenMethod+() = result
169+
}
170+
171+
/** Simplifiable usage of `java.util.concurrent.TimeUnit` */
172+
predicate simplifiableTimeUnitUsage(IntegerLiteral durationValueExpr, DurationUnit unitIn) {
173+
exists(EnumConstant unitConstant |
174+
unitConstant.getDeclaringType() instanceof TypeTimeUnit and
175+
unitConstant.getName() = unitIn.getTimeUnitName()
176+
|
177+
// Calling `to...` method on TimeUnit constant, e.g. `TimeUnit.SECONDS.toMinutes(60)`
178+
exists(MethodAccess call, Method m |
179+
m = call.getMethod() and
180+
m.getDeclaringType() instanceof TypeTimeUnit
181+
|
182+
m.getName().matches("to%") and
183+
m.getParameterType(0).hasName("long") and
184+
m.getReturnType().hasName("long") and
185+
call.getQualifier().(FieldRead).getField() = unitConstant and
186+
durationValueExpr = call.getArgument(0)
187+
)
188+
or
189+
// Calling callable with `value, timeUnit` args, e.g. `timeUnit.convert(60, TimeUnit.SECONDS)`
190+
exists(Call call, Callable callee, int valueParamIndex, int timeUnitParamIndex |
191+
callee = call.getCallee() and
192+
isTimeUnitParam(getAnOverridden(callee).getDeclaringType().getQualifiedName(),
193+
callee.getName(), valueParamIndex, timeUnitParamIndex) and
194+
call.getArgument(timeUnitParamIndex).(FieldRead).getField() = unitConstant and
195+
durationValueExpr = call.getArgument(valueParamIndex)
196+
)
197+
)
198+
}
199+
200+
/** Simplifiable usage of `java.time.Duration` */
201+
predicate simplifiableDurationUsage(IntegerLiteral durationValueExpr, DurationUnit unitIn) {
202+
exists(MethodAccess call, Method m |
203+
m = call.getMethod() and
204+
m.getDeclaringType().hasQualifiedName("java.time", "Duration")
205+
|
206+
m.getName() = unitIn.getDurationMethodName() and
207+
durationValueExpr = call.getArgument(0)
208+
)
209+
}
210+
211+
from
212+
IntegerLiteral durationValueExpr, DurationUnit unitIn, int valueIn, DurationUnit unitOut,
213+
int valueOut, string suggestion
214+
where
215+
(
216+
simplifiableTimeUnitUsage(durationValueExpr, unitIn) and
217+
suggestion = valueOut + " " + unitOut.getTimeUnitName()
218+
or
219+
simplifiableDurationUsage(durationValueExpr, unitIn) and
220+
suggestion = unitOut.getDurationMethodName() + "(" + valueOut + ")"
221+
) and
222+
valueIn = durationValueExpr.getIntValue() and
223+
// Don't try to 'simplify' duration of 0
224+
valueIn != 0 and
225+
simplifyDuration(unitIn, valueIn, unitOut, valueOut)
226+
select durationValueExpr, "Should use instead: " + suggestion

0 commit comments

Comments
 (0)