Skip to content

SONARJAVA-7238: Improve S9395 and resolve conflicts with S1905 and S2184 - #6397

Open
romainbrenguier wants to merge 1 commit into
masterfrom
romain/sonarjava-7238
Open

romainbrenguier wants to merge 1 commit into
masterfrom
romain/sonarjava-7238

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Builds on SONARJAVA-7227 (#6391, already on master), which makes S1905 skip casts documenting a potentially lossy int/long → float or long → double conversion and adds NumericConversionUtils.

  • S9395 / S2184 deduplication: S9395 no longer reports integer arithmetic (+ - * /) in the shapes S2184 already reports: non-parenthesized arithmetic in initializers, plain = assignments, returns and non-varargs arguments, plus integer divisions nested in a floating-point expression. Casting the result ((float) (a / b)) would not fix the integer division, S2184's fix of casting an operand is the right one.
  • Operand-cast message: arithmetic that S2184 does not report (parenthesized operands, compound assignments, varargs arguments) is still reported by S9395, with the message "Cast one of the operands of this ... operation ...".
  • S1905 still reports casts of arithmetic results: the precision-loss suppression from SONARJAVA-7227 no longer applies when the cast operand is + - * /, so redundant casts such as (float) (a * b) are reported again.
  • Description: "How to fix it" now offers alternatives (keep the integer type, use a wider floating-point type or BigDecimal, or cast only when the precision loss is acceptable) and references S2184. Quickfix metadata is set to infeasible, since adding a cast automatically could hide a bug.

The RSPEC repository needs the matching description and metadata update.

Ruling results for S9395 and S1905 are expected to change. CI will produce the ruling update.

🤖 Generated with Claude Code

Agent workflow

Tool link: https://github.com/SonarSource/languages-experimental-tooling/tree/romain/my-tickets/personal/romain-brenguier
PR created using uv run create_with_claude.py

PR updated using uv run update_with_claude.py --prompt "Implement the action plan described in the document Code Review: Improves S9395 and resolves its conflicts with S1905 and S2184 by excluding int/long-to-float casts from S1905 and skipping + - * /` arithmetic in S9395. Changes are requested because the S9395 exclusion currently hides cases S2184 never reports (parenthesized operands, compound assignments, varargs calls), leaving them unflagged by either rule.

  • S9395Check.checkExpression skips all + - * / expressions, but S2184 misses parenthesized operands, compound assignments, and varargs calls; limit the exclusion or extend S2184 and add test cases for these shapes.
  • RedundantTypeCastCheck suppresses S1905 for lossy int arithmetic casts such as (float) (a * b), so no rule reports this redundant cast; apply the same arithmetic exclusion there for consistency.
  1. ⚠️ Bug: S9395 arithmetic exclusion covers cases S2184 never reports
    Files: java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:130-133, java-checks-test-sources/default/src/main/java/checks/S9395CheckSample.java:164-173

    S9395Check.checkExpression now skips every + - * / expression after skipParentheses, on the assumption that S2184 reports them. CastArithmeticOperandCheck (S2184) does not report several of these cases:

    • Parenthesized operands: expressionIsOperationToIntOrLong(expr) runs on the raw expression and does not skip parentheses. So float f = (a * b); or float f = (a + b); is not reported. Only DIVIDE is still caught, through visitBinaryExpression.
    • Compound assignments: visitAssignmentExpression only handles Tree.Kind.ASSIGNMENT, so floatVar += a * b or doubleVar -= longA + longB is not reported.
    • Varargs calls: S2184 requires arguments.size() == parametersTypes.size(), so takeFloatVarargs(a * b, "x", "y") is skipped. S9395 does check fixed varargs parameters.

    S9395 flagged all of these before this PR, and now neither rule reports them. Either limit the S9395 exclusion to the shapes S2184 actually handles, or extend S2184 to cover them. Please also add test cases for these shapes; the sample only covers the parenthesized DIVIDE case.

    Fix (Only skip arithmetic in plain (non-parenthesized) form, as S2184 does. Division is the exception, since S2184's visitBinaryExpression catches it anywhere inside a floating-point expression. Compound assignments and varargs need separate handling, e.g. passing a flag from visitAssignmentExpression and checkArguments.):
    ExpressionTree unwrapped = ExpressionUtils.skipParentheses(expr);
    if (unwrapped.is(Tree.Kind.TYPE_CAST) || unwrapped.is(Tree.Kind.DIVIDE)
    || (coveredByS2184 && expr.is(Tree.Kind.PLUS, Tree.Kind.MINUS, Tree.Kind.MULTIPLY))) {
    return;
    }

  2. 💡 Edge Case: S1905 now hides redundant casts on int arithmetic results
    Files: java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java:93-95, java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:131

    RedundantTypeCastCheck now ignores any cast where isPotentiallyLossyConversion(skipParentheses(expr), castType) is true, whatever the shape of the expression. Take float f = (float) (a * b); or (float) (a + b) with int operands. S9395 no longer asks for this cast, because it skips + - * /. S2184 also stays silent, since the initializer is a TYPE_CAST and not a binary expression, and it only detects nested arithmetic for DIVIDE. Before this PR, S1905 reported the redundant cast. Now no rule reports this pattern, even though the PR description says S1905 still reports truly redundant casts. Applying the same arithmetic exclusion here (do not suppress when the unwrapped operand is + - * /) would keep the two rules consistent.

    Fix (Only suppress casts that S9395 would actually ask for):
    ExpressionTree castOperand = ExpressionUtils.skipParentheses(typeCastTree.expression());
    if (!castOperand.is(Tree.Kind.PLUS, Tree.Kind.MINUS, Tree.Kind.MULTIPLY, Tree.Kind.DIVIDE)
    && NumericConversionUtils.isPotentiallyLossyConversion(castOperand, typeCastTree.type().symbolType())) {
    return false;
    }." -a "Code Review: Improves S9395 and resolves its conflicts with S1905 and S2184 by excluding int/long-to-float casts from S1905 and skipping + - * / arithmetic in S9395. Changes are requested because the S9395 exclusion currently hides cases S2184 never reports (parenthesized operands, compound assignments, varargs calls), leaving them unflagged by either rule.

  • S9395Check.checkExpression skips all + - * / expressions, but S2184 misses parenthesized operands, compound assignments, and varargs calls; limit the exclusion or extend S2184 and add test cases for these shapes.
  • RedundantTypeCastCheck suppresses S1905 for lossy int arithmetic casts such as (float) (a * b), so no rule reports this redundant cast; apply the same arithmetic exclusion there for consistency.
  1. ⚠️ Bug: S9395 arithmetic exclusion covers cases S2184 never reports
    Files: java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:130-133, java-checks-test-sources/default/src/main/java/checks/S9395CheckSample.java:164-173

    S9395Check.checkExpression now skips every + - * / expression after skipParentheses, on the assumption that S2184 reports them. CastArithmeticOperandCheck (S2184) does not report several of these cases:

    • Parenthesized operands: expressionIsOperationToIntOrLong(expr) runs on the raw expression and does not skip parentheses. So float f = (a * b); or float f = (a + b); is not reported. Only DIVIDE is still caught, through visitBinaryExpression.
    • Compound assignments: visitAssignmentExpression only handles Tree.Kind.ASSIGNMENT, so floatVar += a * b or doubleVar -= longA + longB is not reported.
    • Varargs calls: S2184 requires arguments.size() == parametersTypes.size(), so takeFloatVarargs(a * b, "x", "y") is skipped. S9395 does check fixed varargs parameters.

    S9395 flagged all of these before this PR, and now neither rule reports them. Either limit the S9395 exclusion to the shapes S2184 actually handles, or extend S2184 to cover them. Please also add test cases for these shapes; the sample only covers the parenthesized DIVIDE case.

    Fix (Only skip arithmetic in plain (non-parenthesized) form, as S2184 does. Division is the exception, since S2184's visitBinaryExpression catches it anywhere inside a floating-point expression. Compound assignments and varargs need separate handling, e.g. passing a flag from visitAssignmentExpression and checkArguments.):
    ExpressionTree unwrapped = ExpressionUtils.skipParentheses(expr);
    if (unwrapped.is(Tree.Kind.TYPE_CAST) || unwrapped.is(Tree.Kind.DIVIDE)
    || (coveredByS2184 && expr.is(Tree.Kind.PLUS, Tree.Kind.MINUS, Tree.Kind.MULTIPLY))) {
    return;
    }

  2. 💡 Edge Case: S1905 now hides redundant casts on int arithmetic results
    Files: java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java:93-95, java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:131

    RedundantTypeCastCheck now ignores any cast where isPotentiallyLossyConversion(skipParentheses(expr), castType) is true, whatever the shape of the expression. Take float f = (float) (a * b); or (float) (a + b) with int operands. S9395 no longer asks for this cast, because it skips + - * /. S2184 also stays silent, since the initializer is a TYPE_CAST and not a binary expression, and it only detects nested arithmetic for DIVIDE. Before this PR, S1905 reported the redundant cast. Now no rule reports this pattern, even though the PR description says S1905 still reports truly redundant casts. Applying the same arithmetic exclusion here (do not suppress when the unwrapped operand is + - * /) would keep the two rules consistent.

    Fix (Only suppress casts that S9395 would actually ask for):
    ExpressionTree castOperand = ExpressionUtils.skipParentheses(typeCastTree.expression());
    if (!castOperand.is(Tree.Kind.PLUS, Tree.Kind.MINUS, Tree.Kind.MULTIPLY, Tree.Kind.DIVIDE)
    && NumericConversionUtils.isPotentiallyLossyConversion(castOperand, typeCastTree.type().symbolType())) {
    return false;
    }" -g "claude"`

PR updated using uv run update_with_claude.py --prompt "Implement the action plan described in the document /tmp/action_plan_romain/sonarjava-7238.txt." -a "/tmp/action_plan_romain/sonarjava-7238.txt" -g "claude"

Iterated on the PR with uv run ci_loop.py for 1 iterations.
✔️ The PR is now ready for review.

Comment thread java-checks/src/main/java/org/sonar/java/checks/S9395Check.java
Comment thread java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java Outdated
- S9395 no longer reports integer arithmetic in the shapes S2184 reports: non-parenthesized
  arithmetic in initializers, plain assignments, returns and non-varargs arguments, plus
  integer divisions nested in a floating-point expression. Other arithmetic operands are
  reported with a message advising to cast one of the operands.
- S1905 still reports redundant casts of integer arithmetic results, such as (float) (a * b):
  the precision-loss suppression from SONARJAVA-7227 no longer applies to them.
- Reuse NumericConversionUtils from SONARJAVA-7227 in S9395.
- Update S9395 description with alternative fixes and mark quickfix as infeasible.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqube-next

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier marked this pull request as ready for review October 26, 2026 16:49
@gitar-bot

gitar-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 closed / 2 findings

🟡 Medium risk · Changing S9395/S1905 arithmetic handling may suppress or add precision-loss findings for Java numeric conversions, altering which code issues developers see.

Refines S9395 to skip + - * arithmetic only where S2184 reports it and extends the S1905 precision-loss suppression to exclude arithmetic casts, so redundant casts like (float) (a * b) are reported again. Both previously open findings are resolved and no issues remain.

✅ 2 closed
✅ Bug: S9395 arithmetic exclusion covers cases S2184 never reports

📄 java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:130-133 📄 java-checks-test-sources/default/src/main/java/checks/S9395CheckSample.java:164-173
S9395Check.checkExpression now skips every + - * / expression after skipParentheses, on the assumption that S2184 reports them. CastArithmeticOperandCheck (S2184) does not report several of these cases:

  • Parenthesized operands: expressionIsOperationToIntOrLong(expr) runs on the raw expression and does not skip parentheses. So float f = (a * b); or float f = (a + b); is not reported. Only DIVIDE is still caught, through visitBinaryExpression.
  • Compound assignments: visitAssignmentExpression only handles Tree.Kind.ASSIGNMENT, so floatVar += a * b or doubleVar -= longA + longB is not reported.
  • Varargs calls: S2184 requires arguments.size() == parametersTypes.size(), so takeFloatVarargs(a * b, "x", "y") is skipped. S9395 does check fixed varargs parameters.

S9395 flagged all of these before this PR, and now neither rule reports them. Either limit the S9395 exclusion to the shapes S2184 actually handles, or extend S2184 to cover them. Please also add test cases for these shapes; the sample only covers the parenthesized DIVIDE case.

✅ Edge Case: S1905 now hides redundant casts on int arithmetic results

📄 java-checks/src/main/java/org/sonar/java/checks/RedundantTypeCastCheck.java:93-95 📄 java-checks/src/main/java/org/sonar/java/checks/S9395Check.java:131
RedundantTypeCastCheck now ignores any cast where isPotentiallyLossyConversion(skipParentheses(expr), castType) is true, whatever the shape of the expression. Take float f = (float) (a * b); or (float) (a + b) with int operands. S9395 no longer asks for this cast, because it skips + - * /. S2184 also stays silent, since the initializer is a TYPE_CAST and not a binary expression, and it only detects nested arithmetic for DIVIDE. Before this PR, S1905 reported the redundant cast. Now no rule reports this pattern, even though the PR description says S1905 still reports truly redundant casts. Applying the same arithmetic exclusion here (do not suppress when the unwrapped operand is + - * /) would keep the two rules consistent.

Verification Rules 2/2 met Repos 4 inspected

Functional validation No results

Rules 2 passed 1 not applicable

✅ Internal artifact registries
✅ Repository license compliance

Broader impact Repos 4 inspected
Cross-repo 4 repositories inspected 0 open impacts

SonarSource/sonarlint-language-server Inspected
SonarSource/sonar-java-symbolic-execution Inspected
SonarSource/sonarlint-vscode Inspected
SonarSource/sonarlint-intellij 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.

1 participant