Skip to content

SONARJAVA-6744 Implement new rule S9148: "Float.compare" or "Double.compare" should be used for floating-point comparisons - #5910

Merged
romainbrenguier merged 4 commits into
masterfrom
new-rule/SONARJAVA-6744-S9148
Aug 11, 2026
Merged

SONARJAVA-6744 Implement new rule S9148: "Float.compare" or "Double.compare" should be used for floating-point comparisons#5910
romainbrenguier merged 4 commits into
masterfrom
new-rule/SONARJAVA-6744-S9148

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Detect incorrect floating-point comparisons (subtraction and relational operators on float/double) inside compareTo, Comparator.compare methods, and Comparator lambdas. Users should use Double.compare or Float.compare instead to correctly handle NaN and negative zero.

Detect incorrect floating-point comparisons (subtraction and relational
operators on float/double) inside compareTo, Comparator.compare methods,
and Comparator lambdas. Users should use Double.compare or Float.compare
instead to correctly handle NaN and negative zero.
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6744

Comment thread java-checks/src/main/java/org/sonar/java/checks/FloatingPointComparisonCheck.java Outdated
romainbrenguier and others added 2 commits August 10, 2026 15:20
… classes

- Add null-check for MethodTree.block() to skip abstract/interface methods
- Restrict isCompareMethod to classes implementing java.util.Comparator
- Fix column indicator in test sample
- Set quickfix to "infeasible" since no quickfixes are implemented
- Add test cases for abstract methods and non-Comparator utility methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Merge the identical MINUS and relational branches in
  FloatingPointComparisonVisitor into a single condition (fixes S1871).
- Add the autoscan expected baseline for S9148 (hasTP, no FP/FN), taken
  from the failing CI run's artifact, and the matching entry in
  autoscan-diff-by-rules.json.
- Add test samples covering the previously uncovered paths: nested
  non-Comparator lambda, local class inside compareTo, a single
  floating-point operand, and methods that only look like comparison
  methods. Coverage of the check goes from 85.7% to ~98.7% (all lines
  covered, 43 of 44 conditions).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/FloatingPointComparisonCheckSample.java"))
.withCheck(new FloatingPointComparisonCheck())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should use withoutSemantics

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.

Yes, as this rule shouldn't work without semantic (if implemented correctly using matchers), it could be good to add a no-semantic test

@romainbrenguier
romainbrenguier marked this pull request as ready for review August 11, 2026 07:05
Comment on lines +60 to +71
private static boolean isCompareToMethod(MethodTree tree) {
return "compareTo".equals(tree.simpleName().name())
&& returnsInt(tree)
&& hasOneNonPrimitiveParameter(tree);
}

private static boolean isCompareMethod(MethodTree tree) {
return "compare".equals(tree.simpleName().name())
&& returnsInt(tree)
&& tree.parameters().size() == 2
&& tree.symbol().owner().type().isSubtypeOf("java.util.Comparator");
}

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 use org.sonar.plugins.java.api.semantic.MethodMatchers here instead. A single matcher (with MethodMatchers.or) should work

Note: This is a recurring issue across new sonar-java rule PRs. If you used a skill to implement this, please consider updating the skill instructions to include MethodMatchers as the standard approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm using nigel with guidance that contains that:

 ### MethodMatchers                                                          
                                                                              
  Use  MethodMatchers  to match method calls by type, name, and signature:    
                                                                              
    private static final MethodMatchers MY_MATCHER = MethodMatchers.create()  
      .ofTypes("java.util.List")                                              
      .names("add")                                                           
      .withAnyParameters()                                                    
      .build();                                                               
                                                                              
    // In visitNode:                                                          
    if (MY_MATCHER.matches(methodInvocationTree)) {                           
      reportIssue(methodInvocationTree, "Message.");                          
    }           

But that doesn't seem to work. I'll try to debug that for the next rule.

void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/FloatingPointComparisonCheckSample.java"))
.withCheck(new FloatingPointComparisonCheck())

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.

Yes, as this rule shouldn't work without semantic (if implemented correctly using matchers), it could be good to add a no-semantic test

…e duplicate branches

- Refactor to use MethodMatchers.or() instead of manual method matching
- Remove helper methods: isCompareToMethod, isCompareMethod, returnsInt, hasOneNonPrimitiveParameter
- Merge duplicate S1871 branches in visitBinaryExpression into single condition
- Add withoutSemantic test to verify no issues without semantic analysis
- Guard visitNode with semantic model null check

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

Copy link
Copy Markdown

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

LGTM

@romainbrenguier
romainbrenguier merged commit f4323b8 into master Aug 11, 2026
28 of 30 checks passed
@romainbrenguier
romainbrenguier deleted the new-rule/SONARJAVA-6744-S9148 branch August 11, 2026 15:03
@gitar-bot

gitar-bot Bot commented Aug 11, 2026

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

Implements new rule S9148 to detect incorrect floating-point comparisons inside comparison methods and lambdas, addressing null pointer exceptions on abstract methods and false positives on non-Comparator classes.

✅ 2 resolved
Bug: NPE on abstract compareTo/compare methods

📄 java-checks/src/main/java/org/sonar/java/checks/FloatingPointComparisonCheck.java:46-50
isCompareToMethod/isCompareMethod match purely on name, int return type and parameter count, so an abstract int compareTo(T) or int compare(T,T) declared in an interface or abstract class also matches. MethodTree.block() is @Nullable and returns null for such methods, so methodTree.block().accept(...) throws a NullPointerException, causing the check to fail on any file containing such a declaration. Guard with if (methodTree.block() != null) before visiting.

Bug: 'compare' detection does not require Comparator type

📄 java-checks/src/main/java/org/sonar/java/checks/FloatingPointComparisonCheck.java:65-69
isCompareMethod flags any method named compare that returns int and has two parameters, regardless of whether the enclosing class implements java.util.Comparator. Unrelated helper methods such as int compare(double a, double b) in an arbitrary utility class will produce issues even though they are not part of a Comparator contract, leading to false positives. Consider verifying the enclosing class is a subtype of java.util.Comparator (mirroring the lambda handling) or that the method is an override of Comparator.compare.

Implementation Status 🟡 0 / 1 issues implemented
SONARJAVA-6744 — 0 / 1 objectives

The PR does not contain any code changes implementing rule S9148.

  • ⬜ Implement new rule S9148 to require Float.compare or Double.compare for floating-point comparisons
Options

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

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

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