Skip to content

SONARJAVA-6950: Implemented rule S9392 - Redundant range checks should be removed - #6135

Open
romainbrenguier wants to merge 6 commits into
masterfrom
romain/new-rule-s9392-sonarjava-6950
Open

romainbrenguier wants to merge 6 commits into
masterfrom
romain/new-rule-s9392-sonarjava-6950

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

This rule detects redundant range checks on the same variable within a single boolean AND expression. When multiple range comparisons (>, <, >=, <=) reference the same variable, one comparison may be logically implied by another. For example, x >= 0 && x >= 5 contains a redundant check because x >= 5 already implies x >= 0.

…d be removed

This rule detects redundant range checks on the same variable within a
single boolean AND expression. When multiple range comparisons (>, <,
>=, <=) reference the same variable, one comparison may be logically
implied by another. For example, x >= 0 && x >= 5 contains a redundant
check because x >= 5 already implies x >= 0.
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6950

Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
Comment thread new_rule_proposal_S9392.txt Outdated
gitar-bot[bot]

This comment was marked as resolved.

@datadog-sonarsource

datadog-sonarsource Bot commented Sep 14, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 2 Pipeline jobs failed

Build | Build

View more details · View in GitHub Actions

Template is not valid in SonarSource/ci-github-actions/v1/build-maven/action.yml at multiple lines due to JSON parse errors.

Build | Ruling Update and Notify

View more details · View in GitHub Actions

No artifacts were downloaded (temp directory does not exist). Ruling results cannot be synced without artifacts.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 7008db0 | Docs | View more details | Give us feedback!

@github-actions

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #6136

Please review and merge it into your branch.

- Fix double-counting comparisons when sibling is not a range check
- Flip operator for literal-on-left comparisons (e.g. 5 < x)
- Add unknown symbol guard to prevent false positives without semantic
- Skip nested CONDITIONAL_AND nodes to prevent duplicate issues
- Add mutual-implication tie-break for identical comparisons
- Use long instead of int for constant values to prevent truncation
- Remove planning artifact new_rule_proposal_S9392.txt
- Add test cases for literal-on-left, identical comparisons, long
  literals, mixed operators, and non-range operands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
@gitar-bot
gitar-bot Bot dismissed their stale review September 14, 2026 13:57

✅ All blocking issues resolved.

Configure merge blocking

- Fix S3776/S135: Extract helper methods to reduce cognitive complexity
- Fix S2325: Make tryAddComparison static
- Fix S1132: Use string-literal-on-left style in equals() comparisons
- Skip identical comparisons to avoid overlap with S1764
- Use secondary location for implying check instead of normalized text
- Guard against side effects (method calls, assignments) in && chains

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +161 to +167
@Nullable
private static Comparison createComparison(Symbol variable, String operator, @Nullable Long constant, BinaryExpressionTree tree) {
if (!variable.isUnknown() && constant != null) {
return new Comparison(variable, operator, constant, tree);
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Bug: test_without_semantic expects issues the check cannot raise

createComparison returns null whenever variable.isUnknown(), and in no-semantic mode VisitorsBridgeForTests passes a null semantic model so IdentifierTreeImpl.symbol() yields Symbol.UNKNOWN_SYMBOL even for method parameters — the check therefore raises zero issues on RedundantRangeCheckCheckSample.java. RedundantRangeCheckCheckTest.test_without_semantic() nevertheless calls verifyIssues(), and since the sample declares ~20 // Noncompliant expectations, expectations.expectNoIssues() is false and assertMultipleIssues fails the build. Switch that test to verifyNoIssues().

The check needs symbols, so no issues can be raised without semantics.:

@Test
void test_without_semantic() {
  CheckVerifier.newVerifier()
    .onFile(mainCodeSourcesPath("checks/RedundantRangeCheckCheckSample.java"))
    .withCheck(new RedundantRangeCheckCheck())
    .withoutSemantic()
    .verifyNoIssues();
}
  • Apply fix

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

Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java Outdated
romainbrenguier and others added 2 commits September 14, 2026 16:35
- Make hasPotentialSideEffects recursive to detect nested side effects
  like !refresh(), x++ > 3, and assignment expressions within subtrees
- Replace Comparison.isIdenticalTo with SyntacticEquivalence.areEquivalent
  to properly defer to S1764 only for syntactically identical comparisons
- Remove dead isIdenticalTo method from Comparison class
- Add test cases for nested side effects and non-syntactically-identical
  but logically equivalent comparisons (x >= 5 && 5 <= x)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simplify if-then-else to single return statement to resolve SonarQube QG failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqube-next

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier marked this pull request as ready for review September 14, 2026 15:18

@nathsou nathsou 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.

The core implication logic looks sound. I left three non-blocking edge cases for follow-up.


String operator = comparison.operatorToken().text();

if (left.is(Kind.IDENTIFIER) && right.is(Kind.INT_LITERAL, Kind.LONG_LITERAL)) {

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.

Signed constants never reach LiteralUtils: Java represents -10 and +10 as unary expressions, so this kind guard rejects them even though the literal helpers can resolve them. For example, x >= -10 && x >= -5 should report the first comparison but currently does not. Could we accept unary plus/minus constant operands and add regression tests?

}

@Override
public void visitMethodReference(MethodReferenceTree tree) {

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.

This safety check misses expressions that can throw without having conventional side effects. For example, in x >= 0 && array[0] > 0 && x >= 5, reporting/removing x >= 0 makes the array access execute for negative x, potentially introducing an NPE or AIOOBE. Division, casts, and unboxing have the same concern. Could we require intervening operands to be safely evaluable, or conservatively split/skip such chains?

}
if (hasPotentialSideEffects(operand)) {
comparisonsByVariable.clear();
return false;

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.

Clearing and aborting the whole chain also loses safe findings that precede a side-effect boundary. In x >= 0 && x >= 5 && refresh(), the first comparison is redundant and removing it does not change whether refresh() runs, but this reports nothing. Could we segment analysis at unsafe operands instead of discarding comparisons already collected before them?

…Check

Handle unary plus/minus constant operands (e.g. -10, +5) in range check
comparisons, as Java represents these as unary expressions rather than
negative literals. Added regression tests for signed constant scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown
CI failed: CI failed due to a build number fetch error causing an unexpected end of JSON input, and a missing ruling artifacts download in the notification job.

Overview

Two distinct errors occurred across the CI run: a build configuration failure where fetching the build number returned malformed or empty JSON, and a dependent ruling update job failure due to missing workflow artifacts.

Failures

Build Number Fetch Error (confidence: high)

  • Type: infrastructure
  • Affected jobs: 104344084604
  • Related to change: no
  • Root cause: The build script encountered an 'unexpected end of JSON input' while fetching the current build number from repository properties, failing the build configuration step.
  • Suggested fix: Rerun the CI job or verify the availability of the external service providing the build number.

Missing Ruling Artifacts (confidence: high)

  • Type: configuration
  • Affected jobs: 104344299589
  • Related to change: yes
  • Root cause: The ruling update and notify job attempted to download artifacts, but found 0 artifacts because the prerequisite ruling-qa job was skipped or failed to produce them for the pull request context.
  • Suggested fix: Ensure ruling QA jobs correctly run and upload artifacts on pull requests, or update workflow conditions to handle skipped ruling jobs gracefully.

Summary

  • Change-related failures: 1 configuration failure regarding missing ruling artifacts.
  • Infrastructure/flaky failures: 1 infrastructure failure due to unexpected end of JSON input when fetching build numbers.
  • Recommended action: Rerun the CI pipeline to clear transient infrastructure errors, and review workflow conditional logic for ruling notification steps.
Code Review ⚠️ Changes requested 12 resolved / 13 findings

Implements rule S9392 to detect redundant range checks in boolean AND expressions, with 12 issues resolved through iterative fixes. The test for no-semantic mode expects issues the check cannot raise in that mode: test_without_semantic() should call verifyNoIssues() instead of verifyIssues() since variables become Symbol.UNKNOWN_SYMBOL when the semantic model is null, causing all comparisons to be filtered out.

⚠️ Bug: test_without_semantic expects issues the check cannot raise

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:161-167 📄 java-checks/src/test/java/org/sonar/java/checks/RedundantRangeCheckCheckTest.java:32-38

createComparison returns null whenever variable.isUnknown(), and in no-semantic mode VisitorsBridgeForTests passes a null semantic model so IdentifierTreeImpl.symbol() yields Symbol.UNKNOWN_SYMBOL even for method parameters — the check therefore raises zero issues on RedundantRangeCheckCheckSample.java. RedundantRangeCheckCheckTest.test_without_semantic() nevertheless calls verifyIssues(), and since the sample declares ~20 // Noncompliant expectations, expectations.expectNoIssues() is false and assertMultipleIssues fails the build. Switch that test to verifyNoIssues().

The check needs symbols, so no issues can be raised without semantics.
@Test
void test_without_semantic() {
  CheckVerifier.newVerifier()
    .onFile(mainCodeSourcesPath("checks/RedundantRangeCheckCheckSample.java"))
    .withCheck(new RedundantRangeCheckCheck())
    .withoutSemantic()
    .verifyNoIssues();
}
✅ 12 resolved
Bug: Same comparison counted twice when sibling operand isn't a range check

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:63-77
tryAddComparison is called twice per && node — once as (left, right) and once as (right, left) — and each call falls back to expr2 when expr1 is not a range comparison. So for any && where only one side is a range check (e.g. if (flag && x > 0), if (s != null && i < 10)), the single comparison is added to the list twice; the two identical entries then imply each other and two false-positive issues are raised on a perfectly valid condition. Collect each operand exactly once instead of using the two-argument fallback.

Bug: Operator not flipped when the literal is the left operand

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:101-115
extractComparison accepts literal <op> identifier but keeps the original operator text, so 5 < x is recorded as x < 5 (and 0 <= x as x <= 0). Consequently a legitimate range check such as if (5 < x && x < 10) is reported: the fabricated x < 5 "implies" x < 10, so the rule tells the developer to delete the real upper bound, and the message quotes a comparison (x < 5) that does not exist in the source. Invert the operator when the variable is on the right-hand side (<>, <=>=, etc.).

Bug: No guard for unknown symbols: all variables collapse into one group

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:105-119 📄 java-checks/src/test/java/org/sonar/java/checks/RedundantRangeCheckCheckTest.java:34-41 📄 java-checks-test-sources/default/src/main/java/checks/RedundantRangeCheckCheckSample.java:7
Without semantics IdentifierTree.symbol() returns the singleton Symbol.UNKNOWN_SYMBOL (java-frontend/src/main/java/org/sonar/java/model/Symbols.java:172, name() = "!unknown!"), so every identifier becomes the same map key. In RedundantRangeCheckCheckSample.java the compliant line if (x >= 0 && y >= 5) then reports an issue (y >= 5 "implies" x >= 0) with the message text !unknown! >= 5, which makes test_without_semantic fail and produces the same false positives on real code whose bindings cannot be resolved. Return null from extractComparison when the symbol is unknown (or skip the file when the semantic model is absent).

Bug: Nested && chains raise duplicate issues on the same comparison

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:46-60 📄 java-checks-test-sources/default/src/main/java/checks/RedundantRangeCheckCheckSample.java:54 📄 java-checks-test-sources/default/src/main/java/checks/RedundantRangeCheckCheckSample.java:58
visitNode fires for every CONDITIONAL_AND node, and each invocation flattens the whole sub-chain, so in x >= 0 && x >= 5 && x >= 10 the inner x >= 0 && x >= 5 node re-reports x >= 0 that the outer node already reported — the sample encodes this by expecting three messages, two of them identical, for two actual redundancies. Analyse only the top-most && of a chain (skip when the parent, after unwrapping parentheses, is itself a CONDITIONAL_AND) and update the expected messages in the sample accordingly.

Bug: Identical comparisons imply each other, both get flagged

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:50-60 📄 java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:145-153
For equal operator and equal constant, implies returns true in both directions, so if (x > 5 && x > 5) (or two identical checks anywhere in the chain) reports both operands, advising removal of the only check that carries the bound. Add a tie-break so that between mutually-implying comparisons only the later one is reported.

...and 7 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Implements rule S9392 to detect redundant range checks in boolean AND expressions, with 12 issues resolved through iterative fixes. The test for no-semantic mode expects issues the check cannot raise in that mode: `test_without_semantic()` should call `verifyNoIssues()` instead of `verifyIssues()` since variables become `Symbol.UNKNOWN_SYMBOL` when the semantic model is null, causing all comparisons to be filtered out.

1. ⚠️ Bug: test_without_semantic expects issues the check cannot raise
   Files: java-checks/src/main/java/org/sonar/java/checks/RedundantRangeCheckCheck.java:161-167, java-checks/src/test/java/org/sonar/java/checks/RedundantRangeCheckCheckTest.java:32-38

   `createComparison` returns null whenever `variable.isUnknown()`, and in no-semantic mode `VisitorsBridgeForTests` passes a null semantic model so `IdentifierTreeImpl.symbol()` yields `Symbol.UNKNOWN_SYMBOL` even for method parameters — the check therefore raises zero issues on `RedundantRangeCheckCheckSample.java`. `RedundantRangeCheckCheckTest.test_without_semantic()` nevertheless calls `verifyIssues()`, and since the sample declares ~20 `// Noncompliant` expectations, `expectations.expectNoIssues()` is false and `assertMultipleIssues` fails the build. Switch that test to `verifyNoIssues()`.

   Fix (The check needs symbols, so no issues can be raised without semantics.):
   @Test
   void test_without_semantic() {
     CheckVerifier.newVerifier()
       .onFile(mainCodeSourcesPath("checks/RedundantRangeCheckCheckSample.java"))
       .withCheck(new RedundantRangeCheckCheck())
       .withoutSemantic()
       .verifyNoIssues();
   }

Review coverage

Functional validation 1 of 1 objectives covered

Rules No rules evaluated

Auto-approval Not enabled · Set up

Implementation Status ✅ 1 of 1 objectives covered
SONARJAVA-6950 - 1 of 1 objectives covered

This PR implements rule S9392 for redundant range checks along with corresponding test cases, rule documentation, and metadata.

✅ 1 covered here
  • ✅ Implement rule S9392 for redundant range checks

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 → Counting what did not apply, without listing it.
Unblock → Override a blocking verdict and allow merging.

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

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.

2 participants