Skip to content

Commit cd53910

Browse files
authored
Add method-parameter-instead-of-direct-field-access.ql
1 parent 4216d66 commit cd53910

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Finds method parameters where always the same field with the same name as the parameter is used
3+
* as argument for that parameter.
4+
* It could improve readability to remove the parameter and directly access the field in the
5+
* method.
6+
*
7+
* For example:
8+
* ```java
9+
* class MyCollection<E> {
10+
* int size;
11+
*
12+
* public E removeLast() {
13+
* // Note: Passes the field to the instance method
14+
* if (isEmpty(size)) {
15+
* ...
16+
* }
17+
* ...
18+
* }
19+
*
20+
* // Note: Uses the parameter instead of directly the field
21+
* private boolean isEmpty(size) {
22+
* return size == 0;
23+
* }
24+
* }
25+
* ```
26+
*
27+
* Could be written as:
28+
* ```java
29+
* ...
30+
* public E popLast() {
31+
* if (isEmpty()) {
32+
* ...
33+
* }
34+
* ...
35+
* }
36+
*
37+
* private boolean isEmpty() {
38+
* return size == 0;
39+
* }
40+
* ...
41+
* ```
42+
*
43+
* Note that if the field is reassigned while the method is running (possibly concurrently, or directly
44+
* or indirectly by the method), then switching to direct field access within the method could lead to
45+
* an undesired behavior change.
46+
*
47+
* @kind problem
48+
* @id TODO
49+
*/
50+
51+
import java
52+
53+
from Method m, Parameter p, int pIndex, Field f
54+
where
55+
p = m.getParameter(pIndex) and
56+
// Only consider if param and field have same name; using same field regardless of name
57+
// could be coincidence and maybe future code changes will use other args
58+
f.getName() = p.getName() and
59+
// Field must be accessible by method, otherwise cannot refactor it to directly access field
60+
// (e.g. if method is only called by subclasses and field is declared in subclass,
61+
// cannot refactor method)
62+
// Note: Don't have to explicitly check field or method visibility; given that there are calls
63+
// to the method with the field as argument it is most likely also accessible directly by method
64+
f.getDeclaringType() = m.getDeclaringType().getASourceSupertype*() and
65+
// Ignore if parameter is reassigned, switching to direct field access would be behavior change
66+
not p.getAnAccess().isVarWrite() and
67+
forex(MethodCall call | call.getMethod().getSourceDeclaration() = m |
68+
(
69+
// Either not in varargs position
70+
pIndex < m.getNumberOfParameters() - 1
71+
or
72+
// Or there is only a single argument for varargs (or parameter is not varargs)
73+
call.getNumArgument() = m.getNumberOfParameters()
74+
) and
75+
exists(FieldRead fRead | fRead = call.getArgument(pIndex) and fRead.getField() = f |
76+
f.isStatic()
77+
or
78+
// Note: Could also cover "instance field for static method", in which case the method
79+
// could be turned into an instance method, but maybe that should be a separate query?
80+
fRead.isOwnFieldAccess() and call.isOwnMethodCall()
81+
)
82+
) and
83+
// Ignore cases where omitting the parameters from the method signature is not possible
84+
not m.isAbstract() and
85+
not m.overrides(_) and
86+
// And is not overridden
87+
not exists(Method override | override.getAnOverride() = m)
88+
select p, "Can omit method parameter and directly access $@ in method", f, "field of same name"

0 commit comments

Comments
 (0)