Skip to content

SONARJAVA-6053 Fix S112 false positive for checked exception#5648

Draft
rombirli wants to merge 4 commits into
masterfrom
rombirli/SONARJAVA-6053-fp-s112-runtimeexception-wrapping
Draft

SONARJAVA-6053 Fix S112 false positive for checked exception#5648
rombirli wants to merge 4 commits into
masterfrom
rombirli/SONARJAVA-6053-fp-s112-runtimeexception-wrapping

Conversation

@rombirli

@rombirli rombirli commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary by Gitar

  • Rule improvements:
    • Modified RawExceptionCheck to avoid reporting false positives when wrapping checked exceptions in RuntimeException or Error.
    • Added isSimpleWrapping logic to detect when a Throwable is passed as an argument to a NewClassTree constructor.
  • Test coverage:
    • Added test cases to RawExceptionCheckSample.java to reproduce S112 false positives.
    • Added scenarios for wrapping checked exceptions with and without messages or specific causes.
  • Ruling updates:
    • Cleaned up existing ruling files for eclipse-jetty, guava, jboss-ejb3-tutorial, and sonar-server to reflect the improved rule logic.

This will update automatically on new commits.

@sonarqubecloud

sonarqubecloud Bot commented Jun 2, 2026

Copy link
Copy Markdown

Agentic Analysis: Early Results

Agentic Analysis and Context Augmentation are available on your project. Here are some issues that could have been prevented. Follow the links to learn how to put them into action.

25 issue(s) found across 1 file(s):

Rule File Line Message
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 14 Replace generic exceptions with specific library exceptions or a custom exception.
java:S2142 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 51 Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 52 Replace generic exceptions with specific library exceptions or a custom exception.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 60 Replace generic exceptions with specific library exceptions or a custom exception.
java:S2142 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 61 Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 62 Replace generic exceptions with specific library exceptions or a custom exception.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 70 Replace generic exceptions with specific library exceptions or a custom exception.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 79 Replace generic exceptions with specific library exceptions or a custom exception.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 87 Replace generic exceptions with specific library exceptions or a custom exception.
java:S2142 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 88 Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 97 Replace generic exceptions with specific library exceptions or a custom exception.
java:S2142 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 98 Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
java:S112 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 99 Replace generic exceptions with specific library exceptions or a custom exception.
java:S2325 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 103 Make "readFromBlockingSource" a "static" method.
java:S1130 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 103 Remove the declaration of thrown exception 'java.io.IOException', as it cannot be thrown from method's body.
java:S1130 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 103 Remove the declaration of thrown exception 'java.lang.InterruptedException', as it cannot be thrown from method's body.
java:S3400 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 104 Remove this method and declare a constant for this value.
java:S2325 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 107 Make "readPrivilegedValue" a "static" method.
java:S1130 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 107 Remove the declaration of thrown exception 'java.security.PrivilegedActionException', as it cannot be thrown from method's body.
java:S3400 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 108 Remove this method and declare a constant for this value.
java:S2325 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 111 Make "invokeReflectively" a "static" method.
java:S1130 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 111 Remove the declaration of thrown exception 'java.lang.reflect.InvocationTargetException', as it cannot be thrown from method's body.
java:S3400 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 112 Remove this method and declare a constant for this value.
java:S1185 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 175 Remove this method to simply inherit it.
java:S139 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java 175 Move this trailing comment on the previous empty line.

Analyzed by SonarQube Agentic Analysis in 3.1 s

@hashicorp-vault-sonar-prod hashicorp-vault-sonar-prod Bot changed the title SONARJAVA-6053 Fix S112 false positive for checked exception SONARJAVA-6053 Fix S112 false positive for checked exception Jun 2, 2026
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6053

@rombirli rombirli force-pushed the rombirli/SONARJAVA-6053-fp-s112-runtimeexception-wrapping branch from 2720f71 to a8740d8 Compare June 9, 2026 14:20
Comment on lines +131 to +136
private static boolean isSimpleWrapping(NewClassTree tree) {
return WRAPPING_EXCEPTIONS.stream().anyMatch(tree.identifier().symbolType()::is) &&
tree.arguments().stream().anyMatch(argument ->
argument.symbolType().isSubtypeOf(THROWABLE )
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Edge Case: isSimpleWrapping suppresses S112 even when wrapping a generic/raw exception

isSimpleWrapping (RawExceptionCheck.java:131-136) treats any throw new RuntimeException(x) / throw new Error(x) as compliant as long as one argument is a subtype of java.lang.Throwable. It does not verify that the wrapped argument is a specific exception. As a result, wrapping a generic/raw exception is now considered compliant, e.g.:

catch (Exception e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)
catch (Throwable e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)

This exactly matches the limitation the author flagged in the commit message ("allow all wrappings, without considering generic raw exception wrapped"). The corresponding test cases wraps_generic_exception and wraps_throwable, which were previously // Noncompliant, were deleted in RawExceptionCheckSample.java rather than kept as expected detections. Since the whole purpose of S112 is to discourage propagating generic exceptions, this re-introduces false negatives for precisely that pattern. The fix should keep wrapping compliant only when the wrapped cause is itself a non-raw (specific) exception.

Only treat the throw as a compliant wrapping when the wrapped cause is a specific (non-raw) exception, so wrapping a generic Exception/Throwable is still reported.:

private static boolean isSimpleWrapping(NewClassTree tree) {
  return WRAPPING_EXCEPTIONS.stream().anyMatch(tree.identifier().symbolType()::is) &&
    tree.arguments().stream().anyMatch(argument -> {
      Type argType = argument.symbolType();
      return argType.isSubtypeOf(THROWABLE) && !isRawException(argType);
    });
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

private static boolean isSimpleWrapping(NewClassTree tree) {
return WRAPPING_EXCEPTIONS.stream().anyMatch(tree.identifier().symbolType()::is) &&
tree.arguments().stream().anyMatch(argument ->
argument.symbolType().isSubtypeOf(THROWABLE )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: Trailing whitespace in isSubtypeOf(THROWABLE )

Minor: argument.symbolType().isSubtypeOf(THROWABLE ) (RawExceptionCheck.java:134) has a stray space before the closing parenthesis. Harmless but worth cleaning up while editing this method.

Was this helpful? React with 👍 / 👎

@rombirli rombirli deployed to sca-checking June 9, 2026 16:57 — with GitHub Actions Active
@sonarqube-next

sonarqube-next Bot commented Jun 9, 2026

Copy link
Copy Markdown

@gitar-bot

gitar-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown
CI failed: Integration tests failed because the rule S112 changes altered the number of reported issues, causing a baseline mismatch in the ruling regression tests.

Overview

Analysis of the CI logs indicates that the PR changes to rule S112 have triggered regressions in the integration test suite. Two failures were identified across different environments (Linux and Windows), both stemming from discrepancies between actual analysis output and existing test baselines.

Failures

Ruling Integration Test Mismatch (confidence: high)

  • Type: test
  • Affected jobs: All integration test jobs (including JavaRulingTest)
  • Related to change: yes
  • Root cause: The modifications to RawExceptionCheckSample.java and the underlying rule logic altered the set of reported issues. The its/ruling test suite performs a differential analysis check, which failed because the new results (14 differences reported) deviate from the stale static baselines.
  • Suggested fix: Review the 14 new issues generated by the updated rule logic. If these changes are intentional and correct, update the baseline files located in its/ruling/src/test/resources to match the new output.

Summary

  • Change-related failures: 2 (both related to regression test suite failures due to updated rule behavior).
  • Infrastructure/flaky failures: 0 (Develocity connection timeouts noted in Windows logs, but these are secondary to the primary test failure).
  • Recommended action: Validate that the 14 differences identified by the test suite are the expected outcomes of the fix for S112. Once verified, update the reference files in the integration test resource directory and commit those changes to this PR.
Code Review ⚠️ Changes requested 0 resolved / 2 findings

Refines RawExceptionCheck to suppress false positives when wrapping checked exceptions, but the new isSimpleWrapping logic incorrectly suppresses reporting for generic/raw exceptions. Additionally, a minor trailing whitespace issue remains in the THROWABLE subtype check.

⚠️ Edge Case: isSimpleWrapping suppresses S112 even when wrapping a generic/raw exception

📄 java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java:131-136 📄 java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java:83-91

isSimpleWrapping (RawExceptionCheck.java:131-136) treats any throw new RuntimeException(x) / throw new Error(x) as compliant as long as one argument is a subtype of java.lang.Throwable. It does not verify that the wrapped argument is a specific exception. As a result, wrapping a generic/raw exception is now considered compliant, e.g.:

catch (Exception e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)
catch (Throwable e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)

This exactly matches the limitation the author flagged in the commit message ("allow all wrappings, without considering generic raw exception wrapped"). The corresponding test cases wraps_generic_exception and wraps_throwable, which were previously // Noncompliant, were deleted in RawExceptionCheckSample.java rather than kept as expected detections. Since the whole purpose of S112 is to discourage propagating generic exceptions, this re-introduces false negatives for precisely that pattern. The fix should keep wrapping compliant only when the wrapped cause is itself a non-raw (specific) exception.

Only treat the throw as a compliant wrapping when the wrapped cause is a specific (non-raw) exception, so wrapping a generic Exception/Throwable is still reported.
private static boolean isSimpleWrapping(NewClassTree tree) {
  return WRAPPING_EXCEPTIONS.stream().anyMatch(tree.identifier().symbolType()::is) &&
    tree.arguments().stream().anyMatch(argument -> {
      Type argType = argument.symbolType();
      return argType.isSubtypeOf(THROWABLE) && !isRawException(argType);
    });
}
💡 Quality: Trailing whitespace in isSubtypeOf(THROWABLE )

📄 java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java:134

Minor: argument.symbolType().isSubtypeOf(THROWABLE ) (RawExceptionCheck.java:134) has a stray space before the closing parenthesis. Harmless but worth cleaning up while editing this method.

🤖 Prompt for agents
Code Review: Refines RawExceptionCheck to suppress false positives when wrapping checked exceptions, but the new isSimpleWrapping logic incorrectly suppresses reporting for generic/raw exceptions. Additionally, a minor trailing whitespace issue remains in the THROWABLE subtype check.

1. ⚠️ Edge Case: isSimpleWrapping suppresses S112 even when wrapping a generic/raw exception
   Files: java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java:131-136, java-checks-test-sources/default/src/main/java/checks/RawExceptionCheckSample.java:83-91

   `isSimpleWrapping` (RawExceptionCheck.java:131-136) treats any `throw new RuntimeException(x)` / `throw new Error(x)` as compliant as long as one argument is a subtype of `java.lang.Throwable`. It does not verify that the wrapped argument is a *specific* exception. As a result, wrapping a generic/raw exception is now considered compliant, e.g.:
   
   ```java
   catch (Exception e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)
   catch (Throwable e) { throw new RuntimeException(e); }   // now Compliant (was Noncompliant)
   ```
   
   This exactly matches the limitation the author flagged in the commit message ("allow all wrappings, without considering generic raw exception wrapped"). The corresponding test cases `wraps_generic_exception` and `wraps_throwable`, which were previously `// Noncompliant`, were deleted in RawExceptionCheckSample.java rather than kept as expected detections. Since the whole purpose of S112 is to discourage propagating generic exceptions, this re-introduces false negatives for precisely that pattern. The fix should keep wrapping compliant only when the wrapped cause is itself a non-raw (specific) exception.

   Fix (Only treat the throw as a compliant wrapping when the wrapped cause is a specific (non-raw) exception, so wrapping a generic Exception/Throwable is still reported.):
   private static boolean isSimpleWrapping(NewClassTree tree) {
     return WRAPPING_EXCEPTIONS.stream().anyMatch(tree.identifier().symbolType()::is) &&
       tree.arguments().stream().anyMatch(argument -> {
         Type argType = argument.symbolType();
         return argType.isSubtypeOf(THROWABLE) && !isRawException(argType);
       });
   }

2. 💡 Quality: Trailing whitespace in isSubtypeOf(THROWABLE )
   Files: java-checks/src/main/java/org/sonar/java/checks/RawExceptionCheck.java:134

   Minor: `argument.symbolType().isSubtypeOf(THROWABLE )` (RawExceptionCheck.java:134) has a stray space before the closing parenthesis. Harmless but worth cleaning up while editing this method.

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.
Unblock → Override a blocking verdict and allow merging.

Comment with these commands to change:

Auto-apply Compact Unblock
gitar auto-apply:on         
gitar display:verbose         
gitar unblock         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant