Skip to content

Commit f7f8b47

Browse files
committed
Java: Add initial version of empty method query
1 parent 9a8cb1a commit f7f8b47

File tree

8 files changed

+476
-0
lines changed

8 files changed

+476
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# J-D-002: An empty method serves no purpose and may indicate programmer error
2+
3+
An empty method serves no purpose and may indicate programmer error.
4+
5+
## Overview
6+
7+
An empty method may indicate that an implmentation was intended to be provided but was accidentally omitted. When using the method, it will not be clear that it does not provide an implentation and with dynamic dispatch, resolving to a blank method may result in unexpected program behaviour.
8+
9+
## Recommendation
10+
11+
If a method is intended to be left empty, do one of the following to indicate that it is intentionally empty: 1) mark it abstract in an abstract class, 2) place it in an interface (then it can be implicitly abstract), 3) place a comment in that method that lets others know that the implementation was intentionally omitted, or 4) throw an `UnsupportedOperationException` in the method (like is done in `java.util.Collections.add`).
12+
13+
## Example
14+
15+
```java
16+
public class Test {
17+
public void f1() { // COMPLIANT
18+
// intentionally empty
19+
}
20+
21+
public void f2() {} // NON_COMPLIANT
22+
23+
public void f3(){ throw new UnsupportedOperationException(); } // COMPLIANT
24+
25+
public abstract class TestInner {
26+
27+
public abstract void f(); // COMPLIANT - intentionally empty
28+
}
29+
30+
}
31+
```
32+
33+
## Implementation Notes
34+
35+
The rule excludes reporting methods annotated with `org.springframework.aop.Pointcut` or `org.aspectj.lang.annotation.Pointcut`. Such annotations are commonly paired with empty methods and are considered an acceptable use case for an empty method.
36+
37+
## References
38+
- [java.util.Collections.add](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/Collection.html#add(E))
39+
- [Template pattern is a valid empty method use](https://en.wikipedia.org/wiki/Template_method_pattern)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* @id java/empty-method
3+
* @name J-D-002: An empty method serves no purpose and may indicate programmer error
4+
* @description An empty method serves no purpose and makes code less readable. An empty method may
5+
* indicate an error on the part of the developer.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity warning
9+
* @tags correctness
10+
* maintainability
11+
* readability
12+
*/
13+
14+
import java
15+
16+
/**
17+
* Represents a likely a test method, which is either a method that is already
18+
* recognized as a `TestMethod` or something that is likely a JUnit test or
19+
* something in the expected test path for Java tests.
20+
*/
21+
class LikelyTestMethod extends Method {
22+
LikelyTestMethod() {
23+
this.getDeclaringType() instanceof TestClass
24+
or
25+
this instanceof TestMethod
26+
or
27+
this instanceof LikelyJunitTest
28+
or
29+
//standard Maven/Gradle test file discovery filepath
30+
this.getFile().getAbsolutePath().matches("%src/test/java%")
31+
or
32+
this.getDeclaringType() instanceof SurefireTest
33+
}
34+
}
35+
36+
/**
37+
* Classes that are likely part of junit tests (more general than `TestMethod` from `UnitTest.qll`)
38+
* A `Method` that is public, has no parameters,
39+
* has a "void" return type, AND either has a name that starts with "test" OR
40+
* has an annotation that ends with "Test"
41+
*/
42+
class LikelyJunitTest extends Method {
43+
LikelyJunitTest() {
44+
this.isPublic() and
45+
this.getReturnType().hasName("void") and
46+
this.hasNoParameters() and
47+
(
48+
this.getName().matches("JUnit%") or
49+
this.getName().matches("test%") or
50+
this.getAnAnnotation().toString().matches("%Test")
51+
)
52+
}
53+
}
54+
55+
/**
56+
* Maven surefire patterns to consider which files are testcases:
57+
* https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html
58+
*/
59+
class SurefireTest extends Class {
60+
SurefireTest() {
61+
this.getFile().getAbsolutePath().matches("%src/test/java%") and
62+
this.getFile()
63+
.getBaseName()
64+
.matches(["Test%.java", "%Test.java", "%Tests.java", "%TestCase.java"])
65+
}
66+
}
67+
68+
/**
69+
* Frameworks that provide `PointCuts`
70+
* which commonly intentionally annotate empty methods
71+
*/
72+
class PointCutAnnotation extends Annotation {
73+
PointCutAnnotation() {
74+
this.getType().hasQualifiedName("org.aspectj.lang.annotation", "Pointcut")
75+
}
76+
}
77+
78+
/**
79+
* A `Method` from source that is not abstract
80+
*/
81+
class NonAbstractSource extends Method {
82+
NonAbstractSource() {
83+
this.fromSource() and
84+
not this.isAbstract() and
85+
not this instanceof LikelyTestMethod
86+
}
87+
}
88+
89+
from NonAbstractSource m
90+
where
91+
//empty
92+
not exists(m.getBody().getAChild()) and
93+
//permit comment lines explaining why this is empty
94+
m.getNumberOfCommentLines() = 0 and
95+
//permit a javadoc above as well as sufficient reason to leave empty
96+
not exists(Javadoc jd | m.getDoc().getJavadoc() = jd) and
97+
//methods annotated this way are usually intentionally empty
98+
not exists(PointCutAnnotation a | a = m.getAnAnnotation())
99+
select m, "Empty method found."
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| Test.java:16:17:16:18 | f2 | Empty method found. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Language Abuse/EmptyMethod.ql
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import org.aspectj.lang.annotation.Pointcut;
2+
3+
public class Test {
4+
5+
// COMPLIANT
6+
public void f() {
7+
int i = 0;
8+
}
9+
10+
// COMPLIANT
11+
public void f1() {
12+
// intentionally empty
13+
}
14+
15+
// NON_COMPLIANT
16+
public void f2() {}
17+
18+
// COMPLIANT - exception
19+
@Pointcut()
20+
public void f4() {}
21+
22+
public abstract class TestInner {
23+
24+
public abstract void f(); // COMPLIANT - intentionally empty
25+
}
26+
27+
}
28+
29+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/aspectj

0 commit comments

Comments
 (0)