Skip to content

Commit a687312

Browse files
SONARJAVA-6093 Implement S3051 checking the signature of the main method. (#5442)
1 parent abad05a commit a687312

13 files changed

Lines changed: 335 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ruleKey": "S3051",
3+
"hasTruePositives": true,
4+
"falseNegatives": 0,
5+
"falsePositives": 0
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
void main() {
2+
IO.println("compact source");
3+
}
4+
5+
int main(int i) { // Noncompliant
6+
return i * 2;
7+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package checks.mainSignature;
2+
3+
public class MainJava21 {
4+
public static void main(String[] args) { // Compliant
5+
}
6+
7+
void main() { // Noncompliant
8+
}
9+
10+
void main(int args) { // Noncompliant
11+
}
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package checks.mainSignature;
2+
3+
interface NonInstantiable {
4+
int multiply(int a, int b);
5+
6+
default void main() { // Compliant
7+
System.out.println("default");
8+
}
9+
10+
int main(int a); // Noncompliant
11+
12+
static void main(String[] arg) { // Compliant
13+
System.out.println("yep, inside an interface");
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package checks.mainSignature;
2+
3+
public class PrivateMain {
4+
private static void main(String[] args) { // Noncompliant
5+
}
6+
7+
private void main() { // Noncompliant
8+
}
9+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package checks.mainSignature;
2+
3+
public class Sample {
4+
public static class Traditional {
5+
public static void main(String[] args) { // Compliant
6+
System.out.println("traditional");
7+
}
8+
}
9+
10+
public static class NoArg {
11+
public static void main() { // Compliant
12+
System.out.println("no arg");
13+
}
14+
}
15+
16+
public static class Instance {
17+
void main(String[] args) { // Compliant
18+
System.out.println("instance");
19+
}
20+
}
21+
22+
public static class Wrong {
23+
public static void main(String[] args) {
24+
System.out.println(new Wrong().main(42));
25+
}
26+
27+
int main(int x) { // Noncompliant
28+
// ^^^^
29+
return x * x;
30+
}
31+
32+
String main(String x, String y) { // Noncompliant
33+
// ^^^^
34+
return "";
35+
}
36+
}
37+
38+
public static class WrongReturn {
39+
public static int main(String[] args) { // Noncompliant
40+
// ^^^^
41+
return 1;
42+
}
43+
}
44+
45+
@interface MyAnnotation {
46+
String main() default ""; // Noncompliant
47+
// ^^^^
48+
}
49+
50+
enum MyEnum {
51+
A, B;
52+
53+
void main(int x) { // Noncompliant
54+
// ^^^^
55+
}
56+
}
57+
58+
record MyRecord(int value) {
59+
void main(int x) { // Noncompliant
60+
// ^^^^
61+
}
62+
63+
static void main(String[] args) {
64+
}
65+
}
66+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package checks.mainSignature;
2+
3+
public class Varargs {
4+
void main(String... args) {
5+
}
6+
7+
void main(int i, String ... args) { // Noncompliant
8+
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package checks.mainSignature;
2+
3+
// This one is really weird. Doesn't really matter what we report,
4+
// as long as nothing crashes.
5+
6+
public class main {
7+
public main() {}
8+
9+
public main(String[] args) {}
10+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import java.util.List;
20+
import org.sonar.check.Rule;
21+
import org.sonar.java.checks.helpers.MethodTreeUtils;
22+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
23+
import org.sonar.plugins.java.api.tree.MethodTree;
24+
import org.sonar.plugins.java.api.tree.Tree;
25+
26+
27+
/**
28+
* Checks that the "main" method has the correct signature for a program entry point.
29+
*
30+
* <p>Note, that even with a correct signature, the "main" method may not be valid entry point.
31+
* For example, it may be declared in an abstract class or an interface.
32+
*/
33+
@Rule(key = "S3051")
34+
public class MainMethodSignatureCheck extends IssuableSubscriptionVisitor {
35+
36+
private static final String MESSAGE = "\"main\" method should only be used for the program entry point and should have appropriate signature.";
37+
38+
@Override
39+
public List<Tree.Kind> nodesToVisit() {
40+
return List.of(Tree.Kind.METHOD);
41+
}
42+
43+
@Override
44+
public void visitNode(Tree tree) {
45+
MethodTree methodTree = (MethodTree) tree;
46+
if ("main".equals(methodTree.simpleName().name())
47+
&& !MethodTreeUtils.isMainMethod(methodTree, context.getJavaVersion())) {
48+
reportIssue(methodTree.simpleName(), MESSAGE);
49+
}
50+
}
51+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.sonar.java.checks.verifier.CheckVerifier;
21+
22+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
24+
class MainMethodSignatureCheckTest {
25+
26+
private static final MainMethodSignatureCheck CHECK = new MainMethodSignatureCheck();
27+
28+
@Test
29+
void test() {
30+
CheckVerifier.newVerifier()
31+
.onFile(mainCodeSourcesPath("checks/mainSignature/Sample.java"))
32+
.withCheck(CHECK)
33+
.withJavaVersion(25)
34+
.verifyIssues();
35+
}
36+
37+
@Test
38+
void test_java21() {
39+
CheckVerifier.newVerifier()
40+
.onFile(mainCodeSourcesPath("checks/mainSignature/MainJava21.java"))
41+
.withCheck(CHECK)
42+
.withJavaVersion(21)
43+
.verifyIssues();
44+
}
45+
46+
@Test
47+
void nonInstantiable() {
48+
CheckVerifier.newVerifier()
49+
.onFile(mainCodeSourcesPath("checks/mainSignature/NonInstantiable.java"))
50+
.withCheck(CHECK)
51+
.withJavaVersion(25)
52+
.verifyIssues();
53+
}
54+
55+
@Test
56+
void privateMain() {
57+
CheckVerifier.newVerifier()
58+
.onFile(mainCodeSourcesPath("checks/mainSignature/PrivateMain.java"))
59+
.withCheck(CHECK)
60+
.verifyIssues();
61+
}
62+
63+
@Test
64+
void compactSource() {
65+
CheckVerifier.newVerifier()
66+
.onFile(mainCodeSourcesPath("checks/mainSignature/CompactSource.java"))
67+
.withCheck(CHECK)
68+
.withJavaVersion(25)
69+
.verifyIssues();
70+
}
71+
72+
@Test
73+
void varargs() {
74+
CheckVerifier.newVerifier()
75+
.onFile(mainCodeSourcesPath("checks/mainSignature/Varargs.java"))
76+
.withCheck(CHECK)
77+
.withJavaVersion(25)
78+
.verifyIssues();
79+
}
80+
81+
@Test
82+
void constructor() {
83+
// The check will not run, because it does not visit constructor nodes.
84+
CheckVerifier.newVerifier()
85+
.onFile(mainCodeSourcesPath("checks/mainSignature/main.java"))
86+
.withCheck(CHECK)
87+
.verifyNoIssues();
88+
}
89+
}

0 commit comments

Comments
 (0)