Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package checks;

import java.io.IOException;

class MainMethodThrowsExceptionInstanceMainCheckSample {
public void main(String[] args) throws IOException { // Noncompliant {{Remove this throws clause.}}
// ^^^^^^
}

public void main(int a, int b) throws IOException {}

public void example() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package checks;

import java.io.IOException;

class MainMethodThrowsExceptionCheckSample {
static class Bad {
public static void main(String[] args) throws IOException { // Noncompliant {{Remove this throws clause.}}
// ^^^^^^
}
}

// Before Java 25, this is not a program entry point.
static class InstanceMain {
public void main(String[] args) throws IOException {
}
}

static class Good {
public static void main(String[] args) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,7 @@ public void throws_Exception2() throws Exception { // Compliant because override

public static void main(String[] args) throws Exception { //should not raise issue SONARJAVA-671
}

void main() throws Exception { // Compliant, because it is an instance main.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public List<Tree.Kind> nodesToVisit() {
@Override
public void visitNode(Tree tree) {
MethodTree methodTree = (MethodTree) tree;
if (MethodTreeUtils.isMainMethod(methodTree) && !methodTree.throwsClauses().isEmpty()) {
if (MethodTreeUtils.isMainMethod(methodTree, context.getJavaVersion()) && !methodTree.throwsClauses().isEmpty()) {
reportIssue(methodTree.throwsToken(), "Remove this throws clause.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.sonar.java.reporting.FluentReporting;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
Expand All @@ -47,11 +48,13 @@ public class RawExceptionCheck extends BaseTreeVisitor implements JavaFileScanne
"java.lang.RuntimeException");

private FluentReporting context;
private JavaVersion javaVersion;
private final Set<Type> exceptionsThrownByMethodInvocations = new HashSet<>();

@Override
public void scanFile(JavaFileScannerContext context) {
this.context = (FluentReporting) context;
this.javaVersion = context.getJavaVersion();
scan(context.getTree());
}

Expand Down Expand Up @@ -120,8 +123,8 @@ private static boolean isNotOverridden(MethodTree tree) {
return Boolean.FALSE.equals(tree.isOverriding());
}

private static boolean isNotMainMethod(MethodTree tree) {
return !MethodTreeUtils.isMainMethod(tree);
private boolean isNotMainMethod(MethodTree tree) {
return !MethodTreeUtils.isMainMethod(tree, javaVersion);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public List<Tree.Kind> nodesToVisit() {

@Override
public void visitNode(Tree tree) {
if (tree.is(Tree.Kind.CONSTRUCTOR) || !MethodTreeUtils.isMainMethod((MethodTree) tree)) {
if (tree.is(Tree.Kind.CONSTRUCTOR) || !MethodTreeUtils.isMainMethod((MethodTree) tree, context.getJavaVersion())) {
tree.accept(new InvocationVisitor());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public List<Tree.Kind> nodesToVisit() {
@Override
public void visitNode(Tree tree) {
MethodTree methodTree = (MethodTree) tree;
if (isPublic(methodTree) && !MethodTreeUtils.isMainMethod(methodTree)) {
if (isPublic(methodTree) && !MethodTreeUtils.isMainMethod(methodTree, context.getJavaVersion())) {
List<String> thrownCheckedExceptions = getThrownCheckedExceptions(methodTree);
if (thrownCheckedExceptions.size() > 1 && isNotOverridden(methodTree)) {
reportIssue(methodTree.simpleName(), "Refactor this method to throw at most one checked exception instead of: " + String.join(", ", thrownCheckedExceptions));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public List<Tree.Kind> nodesToVisit() {
@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;
if (!ClassPatternsUtils.isUtilityClass(classTree) || ClassPatternsUtils.isPrivateInnerClass(classTree)) {
if (!ClassPatternsUtils.isUtilityClass(classTree, context.getJavaVersion()) || ClassPatternsUtils.isPrivateInnerClass(classTree)) {
return;
}
boolean hasImplicitPublicConstructor = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void scanFile(JavaFileScannerContext context) {
@Override
public void visitClass(ClassTree tree) {
// if class is utility or private inner class -> don't report
if (ClassPatternsUtils.isUtilityClass(tree) || ClassPatternsUtils.isPrivateInnerClass(tree)) {
if (ClassPatternsUtils.isUtilityClass(tree, context.getJavaVersion()) || ClassPatternsUtils.isPrivateInnerClass(tree)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.List;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
Expand All @@ -35,15 +36,16 @@ public static boolean isPrivateInnerClass(ClassTree classTree) {
ModifiersUtils.hasModifier(classTree.modifiers(), Modifier.PRIVATE);
}

public static boolean isUtilityClass(ClassTree classTree) {
/** True if the class contains only static members (with caveats). */
public static boolean isUtilityClass(ClassTree classTree, JavaVersion javaVersion) {
return !anonymousClass(classTree) && hasOnlyStaticMembers(classTree) && !extendsAnotherClassOrImplementsSerializable(classTree)
&& !containsMainMethod(classTree);
&& !containsMainMethod(classTree, javaVersion);
}

private static boolean containsMainMethod(ClassTree classTree) {
private static boolean containsMainMethod(ClassTree classTree, JavaVersion javaVersion) {
return classTree.members().stream()
.filter(member -> member.is(Tree.Kind.METHOD))
.anyMatch(method -> MethodTreeUtils.isMainMethod((MethodTree) method));
.anyMatch(method -> MethodTreeUtils.isMainMethod((MethodTree) method, javaVersion));
}

private static boolean hasOnlyStaticMembers(ClassTree classTree) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.sonar.java.annotations.VisibleForTesting;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.Arguments;
Expand All @@ -48,14 +49,26 @@ public final class MethodTreeUtils {
private MethodTreeUtils() {
}

public static boolean isMainMethod(MethodTree m) {
public static boolean isMainMethod(MethodTree m, JavaVersion javaVersion) {
return javaVersion.isJava25Compatible() ? isMainMethodJava25(m) : isMainMethodTraditional(m);
}

private static boolean isMainMethodJava25(MethodTree m) {
return !isPrivate(m) && isNamed(m, "main") && returnsPrimitive(m, "void") && (hasStringArrayParameter(m) || hasNoParameters(m));
}

private static boolean isMainMethodTraditional(MethodTree m) {
return isPublic(m) && isStatic(m) && isNamed(m, "main") && returnsPrimitive(m, "void") && hasStringArrayParameter(m);
}

private static boolean hasStringArrayParameter(MethodTree m) {
return m.parameters().size() == 1 && isParameterStringArray(m);
}

private static boolean hasNoParameters(MethodTree m) {
return m.parameters().isEmpty();
}

private static boolean isParameterStringArray(MethodTree m) {
VariableTree variableTree = m.parameters().get(0);
boolean result = false;
Expand Down Expand Up @@ -137,6 +150,10 @@ private static boolean isPublic(MethodTree m) {
return ModifiersUtils.hasModifier(m.modifiers(), Modifier.PUBLIC);
}

private static boolean isPrivate(MethodTree m) {
return ModifiersUtils.hasModifier(m.modifiers(), Modifier.PRIVATE);
}

private static boolean returnsInt(MethodTree m) {
return returnsPrimitive(m, "int");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ private static List<JavaQuickFix> createQuickFixes(MethodTree methodTree, List<I
return quickFixes;
}

private static boolean isExcluded(MethodTree tree) {
return MethodTreeUtils.isMainMethod(tree)
private boolean isExcluded(MethodTree tree) {
return MethodTreeUtils.isMainMethod(tree, context.getJavaVersion())
|| isAnnotated(tree)
|| isOverriding(tree)
|| isSerializableMethod(tree)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,25 @@
import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class MainMethodThrowsExceptionCheckTest {

@Test
void test() {
CheckVerifier.newVerifier()
.onFile("src/test/files/checks/MainMethodThrowsExceptionCheck.java")
.onFile(mainCodeSourcesPath("checks/MainMethodThrowsExceptionCheckSample.java"))
.withCheck(new MainMethodThrowsExceptionCheck())
.withJavaVersion(21)
.verifyIssues();
}

@Test
void test_instance_main() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/MainMethodThrowsExceptionCheckInstanceMainSample.java"))
.withCheck(new MainMethodThrowsExceptionCheck())
.withJavaVersion(25)
.verifyIssues();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/RawExceptionCheckSample.java"))
.withCheck(new RawExceptionCheck())
.withJavaVersion(25)
.verifyIssues();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.sonar.java.checks.helpers;

import org.junit.jupiter.api.Test;
import org.sonar.java.model.JavaVersionImpl;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;

Expand All @@ -33,13 +35,19 @@ void is_private_inner_class() {

@Test
void is_utility_class() {
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void main(String[] args){} }")));
assertTrue(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void a(){} private static void b(){}}")));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("enum A {}")));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { enum B {}}")));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { interface B {}}")));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { @interface B {}}")));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void a(){} private static void b(){} private void c(){}}")));
JavaVersion java21 = new JavaVersionImpl(21);
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void main(String[] args){} }"), java21));
assertTrue(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void a(){} private static void b(){}}"), java21));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("enum A {}"), java21));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { enum B {}}"), java21));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { interface B {}}"), java21));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { @interface B {}}"), java21));
assertFalse(ClassPatternsUtils.isUtilityClass(parseClass("class A { public static void a(){} private static void b(){} private void c(){}}"), java21));

JavaVersion java25 = new JavaVersionImpl(25);
ClassTree instanceMain = parseClass("class A { static void main(){} }");
assertTrue(ClassPatternsUtils.isUtilityClass(instanceMain, java21));
assertFalse(ClassPatternsUtils.isUtilityClass(instanceMain, java25));
}

private ClassTree parseClass(String code) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.sonar.java.model.JavaVersionImpl;
import org.sonar.java.model.expression.LiteralTreeImpl;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ClassTree;
Expand All @@ -45,14 +47,28 @@ class MethodTreeUtilsTest {

@Test
void is_main_method() {
assertTrue(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String[] args){} }")));
assertTrue(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String... args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public void main(String[] args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { static void main(String[] args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void amain(String[] args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static int main(String[] args){} }")));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String[] args, String[] second){} }")));
JavaVersion java21 = new JavaVersionImpl(21);

assertTrue(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String[] args){} }"), java21));
assertTrue(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String... args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public void main(String[] args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { static void main(String[] args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void amain(String[] args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static int main(String[] args){} }"), java21));
assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A { public static void main(String[] args, String[] second){} }"), java21));

JavaVersion java25 = new JavaVersionImpl(25);

MethodTree instanceMainNoArg = parseMethod("class A { void main(){} }");
assertFalse(MethodTreeUtils.isMainMethod(instanceMainNoArg, java21));
assertTrue(MethodTreeUtils.isMainMethod(instanceMainNoArg, java25));

MethodTree instanceMainWithArgs = parseMethod("class A { void main(String ... args){} }");
assertFalse(MethodTreeUtils.isMainMethod(instanceMainWithArgs, java21));
assertTrue(MethodTreeUtils.isMainMethod(instanceMainWithArgs, java25));

assertFalse(MethodTreeUtils.isMainMethod(parseMethod("class A {void amain(){} }"), java25));
}

@Test
Expand Down
Loading