Skip to content

SONARJAVA-7156: Implemented rule S9431 Custom exceptions should extend "Exception" or "RuntimeException" rather than "Throwable" - #6331

Open
romainbrenguier wants to merge 4 commits into
masterfrom
romain/new-rule-s9431-sonarjava-7156
Open

romainbrenguier wants to merge 4 commits into
masterfrom
romain/new-rule-s9431-sonarjava-7156

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Detects classes that directly extend java.lang.Throwable and directs developers to extend Exception or RuntimeException instead.

Agent workflow

PR created using uv run new_rule_implementation.py S9431 -j SONARJAVA-7156 -a codex -g AGENTS.md .claude/skills/new-rule/SKILL.md

Tool link: https://github.com/SonarSource/languages-experimental-tooling/tree/romain/my-tickets/personal/romain-brenguier

…d "Exception" or "RuntimeException" rather than "Throwable"

Detects classes that directly extend java.lang.Throwable and directs developers to extend Exception or RuntimeException instead.
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7156

@datadog-sonarsource

This comment has been minimized.

Comment on lines +35 to +41
void does_not_report_without_semantic() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/CustomExceptionExtendsThrowableCheckSample.java"))
.withCheck(new CustomExceptionExtendsThrowableCheck())
.withoutSemantic()
.verifyIssues();
}

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: Test name says 'does_not_report' but it asserts the issues are reported

does_not_report_without_semantic calls .withoutSemantic().verifyIssues() on the same sample file, which has // Noncompliant markers. verifyIssues() fails unless every marker gets an issue, so this test actually checks that the rule still reports without semantic. I checked why that works: JavaCheckVerifier.withoutSemantic() only nulls the Sema in the scanner context (VisitorsBridgeForTests.createScannerContext), while JDT still resolves java.lang.Throwable in the tree. So the assertion passes, but the name says the opposite of what the test checks, and nothing tests the no-semantic case where the rule should stay silent. Fix: rename the test to match what it checks, like reports_without_semantic. If the goal really was 'no issues without semantic', use verifyNoIssues() instead, as JpaEagerFetchTypeCheckTest does.

Rename the test so the name matches the assertion:

@Test
void reports_without_semantic() {
  CheckVerifier.newVerifier()
    .onFile(mainCodeSourcesPath("checks/CustomExceptionExtendsThrowableCheckSample.java"))
    .withCheck(new CustomExceptionExtendsThrowableCheck())
    .withoutSemantic()
    .verifyIssues();
}
  • Apply fix

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

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6335

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@romainbrenguier

Copy link
Copy Markdown
Contributor Author

/pvf

@github-actions

Copy link
Copy Markdown
Contributor

@romainbrenguier ❌ Your /pvf command was not dispatched. See the failed run.

No candidate-version artifact for e2059c1; wait for Build to finish

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6341

@github-actions

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 2 rule files: 0 issues removed, 2 issues added.

S9431 (java) on eclipse-jetty - 0 issues removed, 1 issues added - new ruling file

Added jetty-util/src/main/java/org/eclipse/jetty/util/ConstantThrowable.java (line 25)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/ConstantThrowable.java)
S9431 (java) on guava - 0 issues removed, 1 issues added - new ruling file

Added src/com/google/common/util/concurrent/ServiceManager.java (line 785)

       780 |     @Override protected void doStart() { notifyStarted(); }
       781 |     @Override protected void doStop() { notifyStopped(); }
       782 |   }
       783 | 
       784 |   /** This is never thrown but only used for logging. */
>>>    785 |   private static final class EmptyServiceManagerWarning extends Throwable {}
       786 | }

@romainbrenguier

Copy link
Copy Markdown
Contributor Author

/pvf S9431

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 18 new · 🗑️ 0 lost issues
View dashboard

@romainbrenguier
romainbrenguier marked this pull request as ready for review October 14, 2026 07:37

@rombirli rombirli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please fix ruling and +29k LOC added before review

@sonarqube-next

Copy link
Copy Markdown
Contributor

@gitar-bot

gitar-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 1 closed / 2 findings

🟡 Medium risk · Adding S9431 reports classes directly extending Throwable, which could introduce new findings and affect project quality-gate results.

Adds rule S9431 to flag custom exceptions that directly extend java.lang.Throwable, suggesting Exception or RuntimeException instead. Approved with one suggestion: the test does_not_report_without_semantic in CustomExceptionExtendsThrowableCheckTest actually asserts that issues are still reported, so it should be renamed (e.g. reports_without_semantic) or switched to verifyNoIssues() if silence was intended. Ruling bot duplicate baselines outside expected/ were also cleaned up.

💡 Quality: Test name says 'does_not_report' but it asserts the issues are reported

📄 java-checks/src/test/java/org/sonar/java/checks/CustomExceptionExtendsThrowableCheckTest.java:35-41

does_not_report_without_semantic calls .withoutSemantic().verifyIssues() on the same sample file, which has // Noncompliant markers. verifyIssues() fails unless every marker gets an issue, so this test actually checks that the rule still reports without semantic. I checked why that works: JavaCheckVerifier.withoutSemantic() only nulls the Sema in the scanner context (VisitorsBridgeForTests.createScannerContext), while JDT still resolves java.lang.Throwable in the tree. So the assertion passes, but the name says the opposite of what the test checks, and nothing tests the no-semantic case where the rule should stay silent. Fix: rename the test to match what it checks, like reports_without_semantic. If the goal really was 'no issues without semantic', use verifyNoIssues() instead, as JpaEagerFetchTypeCheckTest does.

Rename the test so the name matches the assertion
@Test
void reports_without_semantic() {
  CheckVerifier.newVerifier()
    .onFile(mainCodeSourcesPath("checks/CustomExceptionExtendsThrowableCheckSample.java"))
    .withCheck(new CustomExceptionExtendsThrowableCheck())
    .withoutSemantic()
    .verifyIssues();
}
✅ 1 closed
✅ Quality: Ruling bot committed ~494 duplicate baselines outside expected/

📄 its/ruling/src/test/resources/guava/java-S1120.json 📄 its/ruling/src/test/resources/eclipse-jetty/java-S1105.json 📄 its/ruling/src/test/resources/guava/java-S9431.json 📄 its/ruling/src/test/resources/eclipse-jetty/java-S9431.json
Commit e2059c1 ("Update ruling results for PR #6331") added 222 java-*.json files under its/ruling/src/test/resources/guava/ and 274 under its/ruling/src/test/resources/eclipse-jetty/, about 92k lines in total. Each one is byte-for-byte the same as the matching file in expected/guava/ or expected/eclipse-jetty/. JavaRulingTest.prepareDumpOldFolder reads baselines only from src/test/resources/expected (line 167). Before this PR, those project folders held only the empty {} java-S3949.json placeholders. The ruling test never reads the new copies, so they are dead files that will drift from the real baselines and mislead anyone who edits them.

The S9431 baselines that matter were added in the right place by bbb6127. Delete every file the bot added under resources/guava/ and resources/eclipse-jetty/ except the existing java-S3949.json files, and check why the ruling-update workflow wrote the full dump into the project folders instead of expected/.

🤖 Prompt for agents
Code Review: Adds rule S9431 to flag custom exceptions that directly extend `java.lang.Throwable`, suggesting `Exception` or `RuntimeException` instead. Approved with one suggestion: the test `does_not_report_without_semantic` in `CustomExceptionExtendsThrowableCheckTest` actually asserts that issues are still reported, so it should be renamed (e.g. `reports_without_semantic`) or switched to `verifyNoIssues()` if silence was intended. Ruling bot duplicate baselines outside `expected/` were also cleaned up.

1. 💡 Quality: Test name says 'does_not_report' but it asserts the issues are reported
   Files: java-checks/src/test/java/org/sonar/java/checks/CustomExceptionExtendsThrowableCheckTest.java:35-41

   `does_not_report_without_semantic` calls `.withoutSemantic().verifyIssues()` on the same sample file, which has `// Noncompliant` markers. `verifyIssues()` fails unless every marker gets an issue, so this test actually checks that the rule *still* reports without semantic. I checked why that works: `JavaCheckVerifier.withoutSemantic()` only nulls the `Sema` in the scanner context (`VisitorsBridgeForTests.createScannerContext`), while JDT still resolves `java.lang.Throwable` in the tree. So the assertion passes, but the name says the opposite of what the test checks, and nothing tests the no-semantic case where the rule should stay silent. Fix: rename the test to match what it checks, like `reports_without_semantic`. If the goal really was 'no issues without semantic', use `verifyNoIssues()` instead, as `JpaEagerFetchTypeCheckTest` does.

   Fix (Rename the test so the name matches the assertion):
   @Test
   void reports_without_semantic() {
     CheckVerifier.newVerifier()
       .onFile(mainCodeSourcesPath("checks/CustomExceptionExtendsThrowableCheckSample.java"))
       .withCheck(new CustomExceptionExtendsThrowableCheck())
       .withoutSemantic()
       .verifyIssues();
   }

Verification Rules 1/1 met PRs 1 inspected

Functional validation No results

Rules 1 passed 2 not applicable

✅ Repository license compliance

Broader impact PRs 1 inspected
Cross-PR 1 PR inspected 0 open impacts

PR #6308: SONARJAVA-7137 Optionally rely on source paths to resolve types during analyses Inspected

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

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

Was this helpful? React with 👍 / 👎 | Gitar

This branch has not been deployed

No deployments
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.

2 participants