diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/CompactSource.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/CompactSource.java new file mode 100644 index 0000000000..dc9ff87b3e --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/CompactSource.java @@ -0,0 +1,7 @@ +void main() { + IO.println("compact source"); +} + +int main(int i) { // Noncompliant + return i * 2; +} diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/NonInstantiable.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/NonInstantiable.java new file mode 100644 index 0000000000..dfadec32a6 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/NonInstantiable.java @@ -0,0 +1,15 @@ +package checks.mainSignature; + +interface NonInstantiable { + int multiply(int a, int b); + + default void main() { // Compliant + System.out.println("default"); + } + + int main(int a); // Noncompliant + + static void main(String[] arg) { // Compliant + System.out.println("yep, inside an interface"); + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/PrivateMain.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/PrivateMain.java new file mode 100644 index 0000000000..8947298745 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/PrivateMain.java @@ -0,0 +1,9 @@ +package checks.mainSignature; + +public class PrivateMain { + private static void main(String[] args) { // Noncompliant + } + + private void main() { // Noncompliant + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/Sample.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/Sample.java new file mode 100644 index 0000000000..6b22fdbeb9 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/Sample.java @@ -0,0 +1,60 @@ +package checks.mainSignature; + +public class Sample { + public static class Traditional { + public static void main(String[] args) { // Compliant + System.out.println("traditional"); + } + } + + public static class NoArg { + public static void main() { // Compliant + System.out.println("no arg"); + } + } + + public static class Instance { + void main(String[] args) { // Compliant + System.out.println("instance"); + } + } + + public static class Wrong { + public static void main(String[] args) { + System.out.println(new Wrong().main(42)); + } + + int main(int x) { // Noncompliant + return x * x; + } + + String main(String x, String y) { // Noncompliant + return ""; + } + } + + public static class WrongReturn { + public static int main(String[] args) { // Noncompliant + return 1; + } + } + + @interface MyAnnotation { + String main() default ""; // Noncompliant + } + + enum MyEnum { + A, B; + + void main(int x) { // Noncompliant + } + } + + record MyRecord(int value) { + void main(int x) { // Noncompliant + } + + static void main(String[] args) { + } + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/Varargs.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/Varargs.java new file mode 100644 index 0000000000..2a8e360935 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/Varargs.java @@ -0,0 +1,10 @@ +package checks.mainSignature; + +public class Varargs { + void main(String... args) { + } + + void main(int i, String ... args) { // Noncompliant + + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/mainSignature/main.java b/java-checks-test-sources/default/src/main/java/checks/mainSignature/main.java new file mode 100644 index 0000000000..abbff780fb --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/mainSignature/main.java @@ -0,0 +1,10 @@ +package checks.mainSignature; + +// This one is really weird. Doesn't really matter what we report, +// as long as nothing crashes. + +public class main { + public main() {} + + public main(String[] args) {} +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/MainMethodSignatureCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MainMethodSignatureCheck.java new file mode 100644 index 0000000000..7e0b3e76fe --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/MainMethodSignatureCheck.java @@ -0,0 +1,83 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.java.model.ModifiersUtils; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.Modifier; +import org.sonar.plugins.java.api.tree.ArrayTypeTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.TypeTree; +import org.sonar.plugins.java.api.tree.VariableTree; + + +/** + * Checks that the "main" method has the correct signature for a program entry point. + * + *
Note, that even with a correct signature, the "main" method may not be valid entry point.
+ * For example, it may be declared in an abstract class or an interface.
+ */
+@Rule(key = "S3051")
+public class MainMethodSignatureCheck extends IssuableSubscriptionVisitor {
+
+ private static final String MESSAGE = "\"main\" method should only be used for the program entry point and should have appropriate signature.";
+
+ @Override
+ public List "A rose by any other name would smell as sweet," but Why is this an issue?
+main by any other name would not. Just because a method has the name "main", that
+doesn’t make it the entry point to an application. It must also have the correct signature. Specifically, it must be public static void
+and accept a single String [] as an argument.Noncompliant code example
+
+public void main(String arg) { // Noncompliant
+ // ...
+}
+
+Compliant solution
+
+public static void main(String [] args) {
+ // ...
+}
+
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S3051.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S3051.json
new file mode 100644
index 0000000000..2771e53898
--- /dev/null
+++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S3051.json
@@ -0,0 +1,15 @@
+{
+ "title": "\"main\" should have the right signature",
+ "type": "BUG",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant\/Issue",
+ "constantCost": "15min"
+ },
+ "tags": [],
+ "defaultSeverity": "Blocker",
+ "ruleSpecification": "RSPEC-3051",
+ "sqKey": "S3051",
+ "scope": "Main",
+ "quickfix": "unknown"
+}