diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java new file mode 100644 index 0000000000..974efeb49a --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckCompactSample.java @@ -0,0 +1,20 @@ +import java.io.IO; + +void main() { + IO.println(""); // Compliant + IO.print(""); // Compliant + f(); + new A().f(); +} + +void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant +} + +class A { + void f() { + IO.println(""); // Compliant + IO.print(""); // Compliant + } +} diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java new file mode 100644 index 0000000000..275bc47a71 --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IoPrintlnUsageCheckSample.java @@ -0,0 +1,14 @@ +package checks; + + +import java.io.IO; +import java.io.PrintStream; + +class IoPrintlnUsageCheckSample { + + void f() { + IO.println(""); // Noncompliant {{Replace this use of IO.println by a logger.}} + IO.print(""); // Noncompliant {{Replace this use of IO.print by a logger.}} + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java index db2a53d2bf..18aece244c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SystemOutOrErrUsageCheck.java @@ -22,7 +22,6 @@ import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.Tree; - import java.util.List; @Rule(key = "S106") @@ -53,11 +52,12 @@ public void visitNode(Tree tree) { private void visitMemberSelectExpression(MemberSelectExpressionTree mset) { String name = mset.identifier().name(); - if ("out".equals(name) && isSystem(mset.expression())) { reportIssue(mset, "Replace this use of System.out by a logger."); } else if ("err".equals(name) && isSystem(mset.expression())) { reportIssue(mset, "Replace this use of System.err by a logger."); + } else if (isIoPrintFunction(mset)) { + reportIssue(mset, "Replace this use of IO." + mset.identifier().name() + " by a logger."); } } @@ -70,4 +70,12 @@ private static boolean isSystem(ExpressionTree expression) { } return identifierTree != null && "System".equals(identifierTree.name()); } + + private static boolean isIoPrintFunction(MemberSelectExpressionTree mset) { + boolean isIoFunction = mset.expression().parent() instanceof MemberSelectExpressionTree tree && + tree.expression() instanceof IdentifierTree identifierTree && + "IO".equals(identifierTree.name()); + // use contains to cover both print and println methods + return isIoFunction && mset.identifier().name().contains("print"); + } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java index 81b19e26b6..43e91a8cf5 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/SystemOutOrErrUsageCheckTest.java @@ -20,10 +20,11 @@ import org.sonar.java.checks.verifier.CheckVerifier; import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; +import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; class SystemOutOrErrUsageCheckTest { @Test - void test() { + void test_sout() { CheckVerifier.newVerifier() .onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) @@ -31,13 +32,29 @@ void test() { } @Test - void test_compact_source_file() { + void test_sout_compact_source_file() { CheckVerifier.newVerifier() .onFile(mainCodeSourcesPath("checks/SystemOutOrErrUsageCheckCompactOnlyMainSample.java")) .withCheck(new SystemOutOrErrUsageCheck()) .verifyNoIssues(); } + @Test + void test_io() { + CheckVerifier.newVerifier() + .onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckSample.java")) + .withCheck(new SystemOutOrErrUsageCheck()) + .verifyIssues(); + } + + @Test + void test_io_compact_source_file() { + CheckVerifier.newVerifier() + .onFile(nonCompilingTestSourcesPath("checks/IoPrintlnUsageCheckCompactSample.java")) + .withCheck(new SystemOutOrErrUsageCheck()) + .verifyNoIssues(); + } + @Test void test_compact_source_file_with_regular_class() { CheckVerifier.newVerifier() diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html index 8fb27cfa2a..3075f0bd81 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S106.html @@ -7,14 +7,15 @@

Why is this an issue?

  • properly recorded
  • securely logged when dealing with sensitive data
  • -

    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 -a dedicated logger is highly recommended.

    +

    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 +using a dedicated logger is highly recommended.

    Code examples

    The following noncompliant code:

     class MyClass {
       public void doSomething() {
         System.out.println("My Message");  // Noncompliant, output directly to System.out without a logger
    +    IO.println("Second Message"); // Noncompliant, same problem, but using Java 25 syntax.
       }
     }
     
    @@ -27,9 +28,8 @@

    Code examples

    Logger logger = Logger.getLogger(getClass().getName()); public void doSomething() { - // ... logger.info("My Message"); // Compliant, output via logger - // ... + logger.info("Second Message"); // Compliant, output via logger } } diff --git a/sonarpedia.json b/sonarpedia.json index 25ce6aca3e..daa120100b 100644 --- a/sonarpedia.json +++ b/sonarpedia.json @@ -3,7 +3,7 @@ "languages": [ "JAVA" ], - "latest-update": "2026-01-26T14:42:25.031525200Z", + "latest-update": "2026-02-10T09:09:57.194517400Z", "options": { "no-language-in-filenames": true, "preserve-filenames": false