Skip to content

Commit c98387c

Browse files
SONARJAVA-4650 S6817: Use of the @async annotation on methods declare… (#4519)
1 parent fdb9bc7 commit c98387c

9 files changed

Lines changed: 222 additions & 6 deletions

File tree

its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,15 @@ public void javaCheckTestSources() throws Exception {
180180
softly.assertThat(newTotal).isEqualTo(knownTotal);
181181
softly.assertThat(rulesCausingFPs).hasSize(6);
182182
softly.assertThat(rulesNotReporting).hasSize(7);
183-
softly.assertThat(rulesSilenced).hasSize(76);
183+
softly.assertThat(rulesSilenced).hasSize(77);
184184

185185
/**
186186
* 4. Check total number of differences (FPs + FNs)
187187
*
188188
* No differences would mean that we find the same issues with and without the bytecode and libraries
189189
*/
190190
String differences = Files.readString(pathFor(TARGET_ACTUAL + PROJECT_KEY + "-no-binaries_differences"));
191-
softly.assertThat(differences).isEqualTo("Issues differences: 3481");
191+
softly.assertThat(differences).isEqualTo("Issues differences: 3483");
192192

193193
softly.assertAll();
194194
}

its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,6 +2909,12 @@
29092909
"falseNegatives": 5,
29102910
"falsePositives": 0
29112911
},
2912+
{
2913+
"ruleKey": "S6817",
2914+
"hasTruePositives": false,
2915+
"falseNegatives": 2,
2916+
"falsePositives": 0
2917+
},
29122918
{
29132919
"ruleKey": "S6818",
29142920
"hasTruePositives": false,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package checks.spring;
2+
3+
import javax.annotation.Nullable;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.scheduling.annotation.Async;
6+
7+
@Configuration
8+
public class AsyncMethodsOnConfigurationClassCheck {
9+
10+
@Async // Noncompliant [[sc=3;ec=9;quickfixes=qf1]] {{Remove this "@Async" annotation from this method.}}
11+
// fix@qf1 {{Remove "@Async"}}
12+
// edit@qf1 [[sc=3;ec=9]] {{}}
13+
public void asyncMethod() {
14+
}
15+
16+
public void method() { // Compliant
17+
}
18+
19+
@Nullable
20+
@Async // Noncompliant
21+
public void someMethod() {
22+
}
23+
24+
}
25+
26+
class NotAConfigurationClass {
27+
28+
@Async // Compliant
29+
public void asyncMethod() {
30+
}
31+
32+
}

java-checks/src/main/java/org/sonar/java/checks/CheckList.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,13 @@
136136
import org.sonar.java.checks.serialization.SerializableObjectInSessionCheck;
137137
import org.sonar.java.checks.serialization.SerializableSuperConstructorCheck;
138138
import org.sonar.java.checks.spring.AsyncMethodsCalledViaThisCheck;
139+
import org.sonar.java.checks.spring.AsyncMethodsOnConfigurationClassCheck;
139140
import org.sonar.java.checks.spring.AsyncMethodsReturnTypeCheck;
140-
import org.sonar.java.checks.spring.AutowiredOnMultipleConstructorsCheck;
141141
import org.sonar.java.checks.spring.AutowiredOnConstructorWhenMultipleConstructorsCheck;
142+
import org.sonar.java.checks.spring.AutowiredOnMultipleConstructorsCheck;
142143
import org.sonar.java.checks.spring.ControllerWithSessionAttributesCheck;
143-
import org.sonar.java.checks.spring.ModelAttributeNamingConventionForSpELCheck;
144144
import org.sonar.java.checks.spring.FieldDependencyInjectionCheck;
145+
import org.sonar.java.checks.spring.ModelAttributeNamingConventionForSpELCheck;
145146
import org.sonar.java.checks.spring.OptionalRestParametersShouldBeObjectsCheck;
146147
import org.sonar.java.checks.spring.PersistentEntityUsedAsRequestParameterCheck;
147148
import org.sonar.java.checks.spring.RequestMappingMethodPublicCheck;
@@ -283,6 +284,7 @@ public final class CheckList {
283284
AssertionsInProductionCodeCheck.class,
284285
AssertsOnParametersOfPublicMethodCheck.class,
285286
AsyncMethodsCalledViaThisCheck.class,
287+
AsyncMethodsOnConfigurationClassCheck.class,
286288
AsyncMethodsReturnTypeCheck.class,
287289
AtLeastOneConstructorCheck.class,
288290
AuthorizationsStrongDecisionsCheck.class,
@@ -945,8 +947,7 @@ public final class CheckList {
945947
UnusedPrivateFieldCheck.class,
946948
VerifiedServerHostnamesCheck.class,
947949
VolatileNonPrimitiveFieldCheck.class,
948-
WeakSSLContextCheck.class
949-
);
950+
WeakSSLContextCheck.class);
950951

951952
private CheckList() {
952953
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2023 SonarSource SA
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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.java.checks.spring;
21+
22+
import java.util.List;
23+
import org.sonar.check.Rule;
24+
import org.sonar.java.checks.helpers.QuickFixHelper;
25+
import org.sonar.java.reporting.JavaQuickFix;
26+
import org.sonar.java.reporting.JavaTextEdit;
27+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
28+
import org.sonar.plugins.java.api.tree.ClassTree;
29+
import org.sonar.plugins.java.api.tree.MethodTree;
30+
import org.sonar.plugins.java.api.tree.Tree;
31+
32+
@Rule(key = "S6817")
33+
public class AsyncMethodsOnConfigurationClassCheck extends IssuableSubscriptionVisitor {
34+
35+
@Override
36+
public List<Tree.Kind> nodesToVisit() {
37+
return List.of(Tree.Kind.CLASS);
38+
}
39+
40+
@Override
41+
public void visitNode(Tree tree) {
42+
ClassTree classTree = (ClassTree) tree;
43+
boolean isConfiguration = classTree.modifiers().annotations().stream()
44+
.anyMatch(annotation -> annotation.annotationType().symbolType().is("org.springframework.context.annotation.Configuration"));
45+
46+
if (isConfiguration) {
47+
classTree.members().stream()
48+
.filter(member -> member.is(Tree.Kind.METHOD))
49+
.map(MethodTree.class::cast)
50+
.forEach(member -> member.modifiers().annotations().stream()
51+
.filter(annotation -> annotation.annotationType().symbolType().is("org.springframework.scheduling.annotation.Async"))
52+
.findFirst()
53+
.ifPresent(annotation -> QuickFixHelper.newIssue(context)
54+
.forRule(this)
55+
.onTree(annotation)
56+
.withMessage("Remove this \"@Async\" annotation from this method.")
57+
.withQuickFix(() -> JavaQuickFix.newQuickFix("Remove \"@Async\"")
58+
.addTextEdit(JavaTextEdit.removeTree(annotation))
59+
.build())
60+
.report()));
61+
}
62+
}
63+
64+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<h2>Why is this an issue?</h2>
2+
<p><code>@Configuration</code> is a class-level annotation indicating that an object is a source of bean definitions. <code>@Configuration</code>
3+
classes declare beans through <code>@Bean</code>-annotated methods. Calls to <code>@Bean</code> methods on <code>@Configuration</code> classes can
4+
also be used to define inter-bean dependencies. The <code>@Bean</code> annotation indicates that a method instantiates, configures, and initializes a
5+
new object to be managed by the Spring IoC container.</p>
6+
<p>Annotating a method of a bean with <code>@Async</code> will make it execute in a separate thread. In other words, the caller will not wait for the
7+
completion of the called method.</p>
8+
<p>The <code>@Async</code> annotation is not supported on methods declared within a <code>@Configuration</code> class. This is because
9+
<code>@Async</code> methods are typically used for asynchronous processing, and they require certain infrastructure to be set up, which may not be
10+
available or appropriate in a <code>@Configuration</code> class.</p>
11+
<h2>How to fix it</h2>
12+
<p>Don’t use <code>@Async</code> annotations on methods of <code>@Configuration</code> classes.</p>
13+
<h3>Code examples</h3>
14+
<h4>Noncompliant code example</h4>
15+
<pre data-diff-id="1" data-diff-type="noncompliant">
16+
@EnableAsync
17+
@Configuration
18+
public class MyConfiguration {
19+
20+
@Async // Noncompliant - This is not allowed
21+
public void asyncMethod() {
22+
// ...
23+
}
24+
}
25+
</pre>
26+
<h4>Compliant solution</h4>
27+
<pre data-diff-id="1" data-diff-type="compliant">
28+
@EnableAsync
29+
@Configuration
30+
public class MyConfiguration {
31+
32+
public void method() {
33+
// ...
34+
}
35+
}
36+
</pre>
37+
<h2>Resources</h2>
38+
<h3>Documentation</h3>
39+
<ul>
40+
<li> <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html">Spring
41+
Framework - @Async</a> </li>
42+
<li> <a href="https://docs.spring.io/spring-framework/reference/core/beans/java/configuration-annotation.html">Spring Framework - Using the
43+
@Configuration annotation</a> </li>
44+
<li> <a href="https://docs.spring.io/spring-framework/reference/core/beans/java/basic-concepts.html">Spring Framework - Basic Concepts: @Bean and
45+
@Configuration</a> </li>
46+
</ul>
47+
<h3>Articles &amp; blog posts</h3>
48+
<ul>
49+
<li> <a href="https://www.baeldung.com/spring-async">Baeldung - How To Do @Async in Spring</a> </li>
50+
</ul>
51+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"title": "Use of the \"@Async\" annotation on methods declared within a \"@Configuration\" class in Spring Boot",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [
10+
"spring"
11+
],
12+
"defaultSeverity": "Major",
13+
"ruleSpecification": "RSPEC-6817",
14+
"sqKey": "S6817",
15+
"scope": "Main",
16+
"quickfix": "covered",
17+
"code": {
18+
"impacts": {
19+
"RELIABILITY": "HIGH"
20+
},
21+
"attribute": "LOGICAL"
22+
}
23+
}

java-checks/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@
486486
"S6810",
487487
"S6813",
488488
"S6814",
489+
"S6817",
489490
"S6818",
490491
"S6829",
491492
"S6837"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2023 SonarSource SA
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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.java.checks.spring;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.sonar.java.checks.verifier.internal.InternalCheckVerifier;
24+
25+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
26+
27+
class AsyncMethodsOnConfigurationClassCheckTest {
28+
29+
@Test
30+
void test() {
31+
InternalCheckVerifier.newInstance()
32+
.onFile(mainCodeSourcesPath("checks/spring/AsyncMethodsOnConfigurationClassCheck.java"))
33+
.withCheck(new AsyncMethodsOnConfigurationClassCheck())
34+
.withQuickFixes()
35+
.verifyIssues();
36+
}
37+
38+
}

0 commit comments

Comments
 (0)