Skip to content

Commit 25ed53b

Browse files
authored
SONARJAVA-6090 Detect usage of io in rule S106 (#5435)
1 parent 78a6b1f commit 25ed53b

6 files changed

Lines changed: 68 additions & 9 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import java.io.IO;
2+
3+
void main() {
4+
IO.println(""); // Compliant
5+
IO.print(""); // Compliant
6+
f();
7+
new A().f();
8+
}
9+
10+
void f() {
11+
IO.println(""); // Compliant
12+
IO.print(""); // Compliant
13+
}
14+
15+
class A {
16+
void f() {
17+
IO.println(""); // Compliant
18+
IO.print(""); // Compliant
19+
}
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package checks;
2+
3+
4+
import java.io.IO;
5+
import java.io.PrintStream;
6+
7+
class IoPrintlnUsageCheckSample {
8+
9+
void f() {
10+
IO.println(""); // Noncompliant {{Replace this use of IO.println by a logger.}}
11+
IO.print(""); // Noncompliant {{Replace this use of IO.print by a logger.}}
12+
}
13+
14+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.sonar.plugins.java.api.tree.IdentifierTree;
2323
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
2424
import org.sonar.plugins.java.api.tree.Tree;
25-
2625
import java.util.List;
2726

2827
@Rule(key = "S106")
@@ -53,11 +52,12 @@ public void visitNode(Tree tree) {
5352

5453
private void visitMemberSelectExpression(MemberSelectExpressionTree mset) {
5554
String name = mset.identifier().name();
56-
5755
if ("out".equals(name) && isSystem(mset.expression())) {
5856
reportIssue(mset, "Replace this use of System.out by a logger.");
5957
} else if ("err".equals(name) && isSystem(mset.expression())) {
6058
reportIssue(mset, "Replace this use of System.err by a logger.");
59+
} else if (isIoPrintFunction(mset)) {
60+
reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger.");
6161
}
6262
}
6363

@@ -70,4 +70,12 @@ private static boolean isSystem(ExpressionTree expression) {
7070
}
7171
return identifierTree != null && "System".equals(identifierTree.name());
7272
}
73+
74+
private static boolean isIoPrintFunction(MemberSelectExpressionTree mset) {
75+
boolean isIoFunction = mset.expression().parent() instanceof MemberSelectExpressionTree tree &&
76+
tree.expression() instanceof IdentifierTree identifierTree &&
77+
"IO".equals(identifierTree.name());
78+
// use contains to cover both print and println methods
79+
return isIoFunction && mset.identifier().name().contains("print");
80+
}
7381
}

java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,41 @@
2020
import org.sonar.java.checks.verifier.CheckVerifier;
2121

2222
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath;
2324

2425
class SystemOutOrErrUsageCheckTest {
2526
@Test
26-
void test() {
27+
void test_sout() {
2728
CheckVerifier.newVerifier()
2829
.onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckSample.java"))
2930
.withCheck(new SystemOutOrErrUsageCheck())
3031
.verifyIssues();
3132
}
3233

3334
@Test
34-
void test_compact_source_file() {
35+
void test_sout_compact_source_file() {
3536
CheckVerifier.newVerifier()
3637
.onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckCompactOnlyMainSample.java"))
3738
.withCheck(new SystemOutOrErrUsageCheck())
3839
.verifyNoIssues();
3940
}
4041

42+
@Test
43+
void test_io() {
44+
CheckVerifier.newVerifier()
45+
.onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckSample.java"))
46+
.withCheck(new SystemOutOrErrUsageCheck())
47+
.verifyIssues();
48+
}
49+
50+
@Test
51+
void test_io_compact_source_file() {
52+
CheckVerifier.newVerifier()
53+
.onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java"))
54+
.withCheck(new SystemOutOrErrUsageCheck())
55+
.verifyNoIssues();
56+
}
57+
4158
@Test
4259
void test_compact_source_file_with_regular_class() {
4360
CheckVerifier.newVerifier()

sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ <h2>Why is this an issue?</h2>
77
<li> properly recorded </li>
88
<li> securely logged when dealing with sensitive data </li>
99
</ul>
10-
<p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err). That is why defining and using
11-
a dedicated logger is highly recommended.</p>
10+
<p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err, IO). That is why defining and
11+
using a dedicated logger is highly recommended.</p>
1212
<h3>Code examples</h3>
1313
<p>The following noncompliant code:</p>
1414
<pre data-diff-id="1" data-diff-type="noncompliant">
1515
class MyClass {
1616
public void doSomething() {
1717
System.out.println("My Message"); // Noncompliant, output directly to System.out without a logger
18+
IO.println("Second Message"); // Noncompliant, same problem, but using Java 25 API.
1819
}
1920
}
2021
</pre>
@@ -27,9 +28,8 @@ <h3>Code examples</h3>
2728
Logger logger = Logger.getLogger(getClass().getName());
2829

2930
public void doSomething() {
30-
// ...
3131
logger.info("My Message"); // Compliant, output via logger
32-
// ...
32+
logger.info("Second Message"); // Compliant, output via logger
3333
}
3434
}
3535
</pre>

sonarpedia.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"languages": [
44
"JAVA"
55
],
6-
"latest-update": "2026-01-26T14:42:25.031525200Z",
6+
"latest-update": "2026-02-10T09:09:57.194517400Z",
77
"options": {
88
"no-language-in-filenames": true,
99
"preserve-filenames": false

0 commit comments

Comments
 (0)