Skip to content

SONARJAVA-6825: Implement rule S9360 - Constant expressions and comparisons should be simplified - #6036

Open
romainbrenguier wants to merge 9 commits into
masterfrom
romain/new-rule-s9360-sonarjava-6825
Open

SONARJAVA-6825: Implement rule S9360 - Constant expressions and comparisons should be simplified#6036
romainbrenguier wants to merge 9 commits into
masterfrom
romain/new-rule-s9360-sonarjava-6825

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Summary

  • Implements rule S9360: Constant expressions and comparisons should be simplified
  • Yoda conditions: Detects comparisons where a literal is on the left side (0 == count, null != obj) and suggests putting the variable on the left
  • Constant Math calls: Detects calls to deterministic Math methods where all arguments are compile-time literals (Math.max(5, 10), Math.sqrt(16.0), Math.abs(-5)) and suggests replacing with precomputed values
  • Covers all comparison operators: ==, !=, <, >, <=, >=
  • Handles unary minus/plus in Math call arguments (e.g., Math.abs(-10))
  • Uses MethodMatchers for type-safe Math method detection with semantic analysis

Test plan

  • Unit tests pass (2 tests: with semantic, non-compiling)
  • Sonar way profile test passes
  • Ruling QA CI checks pass (ruling expectation files will be auto-generated)

🤖 Generated with Claude Code

romainbrenguier and others added 3 commits August 24, 2026 16:54
…risons should be simplified

This rule detects Yoda conditions where a constant literal appears on the
left side of a comparison operator (==, !=, <, >). It reports an issue
with the message 'Put the variable on the left side of this comparison.'

The rule handles all literal types: integers, longs, floats, doubles,
booleans, characters, strings, and null. It correctly skips parentheses
to detect Yoda conditions in nested expressions like ((0) == count).

Test coverage includes:
- All literal types with == and != operators
- Less than and greater than operators
- Nested parentheses handling
- Non-comparison contexts (assignments, arithmetic, method calls)
- Edge cases: both literals, both variables, ternary operators
Extend YodaConditionCheck to also detect calls to deterministic Math
methods where all arguments are compile-time constant literals (e.g.,
Math.max(5, 10), Math.sqrt(16.0)). Add support for <= and >= comparison
operators in Yoda condition detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

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

Copy link
Copy Markdown
Contributor

SONARJAVA-6825

@datadog-sonarsource

This comment has been minimized.

Comment thread sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java Outdated
gitar-bot[bot]

This comment was marked as resolved.

Compare null to a reference type (Object) instead of a primitive (int)
to fix illegal operand types for binary operator '=='.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot
gitar-bot Bot dismissed their stale review August 25, 2026 13:56

✅ Code review updated (blocking issues remain unresolved).

Configure merge blocking

@github-actions

Copy link
Copy Markdown
Contributor

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

Please review and merge it into your branch.

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

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

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

S9360 (java) on commons-beanutils - 0 issues removed, 4 issues added - new ruling file

Added src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java (line 106)

       101 |             NoSuchMethodException,
       102 |             IllegalAccessException,
       103 |             InvocationTargetException,
       104 |             InstantiationException {
       105 | 
>>>    106 |         if (null == args) {
       107 |             args = EMPTY_OBJECT_ARRAY;
       108 |         }
       109 |         final int arguments = args.length;
       110 |         final Class<?> parameterTypes[] = new Class<?>[arguments];
       111 |         for (int i = 0; i < arguments; i++) {

Added src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java (line 154)

       149 |             args = EMPTY_OBJECT_ARRAY;
       150 |         }
       151 | 
       152 |         final Constructor<T> ctor =
       153 |             getMatchingAccessibleConstructor(klass, parameterTypes);
>>>    154 |         if (null == ctor) {
       155 |             throw new NoSuchMethodException(
       156 |                 "No such accessible constructor on object: " + klass.getName());
       157 |         }
       158 |         return ctor.newInstance(args);
       159 |     }

Added src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java (line 218)

       213 |             NoSuchMethodException,
       214 |             IllegalAccessException,
       215 |             InvocationTargetException,
       216 |             InstantiationException {
       217 | 
>>>    218 |         if (null == args) {
       219 |             args = EMPTY_OBJECT_ARRAY;
       220 |         }
       221 |         final int arguments = args.length;
       222 |         final Class<?> parameterTypes[] = new Class[arguments];
       223 |         for (int i = 0; i < arguments; i++) {
S9360 (java) on eclipse-jetty - 0 issues removed, 55 issues added - new ruling file

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 343)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 358)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 373)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java (line 1028)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 431)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 436)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 445)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java (line 93)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java (line 414)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java)

Added jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java (line 94)

(source file not found at this revision: jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java (line 744)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java (line 835)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java)

Added jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java (line 1275)

(source file not found at this revision: jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java (line 901)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java (line 907)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java)
S9360 (java) on eclipse-jetty-similar-to-main - 0 issues removed, 45 issues added - new ruling file

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 343)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 358)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java (line 373)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java (line 1028)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 431)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 436)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java (line 445)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java (line 93)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java)

Added jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java (line 414)

(source file not found at this revision: jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java)

Added jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java (line 94)

(source file not found at this revision: jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java (line 744)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java (line 835)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java)
S9360 (java) on guava - 0 issues removed, 7 issues added - new ruling file

Added src/com/google/common/base/SmallCharMatcher.java (line 61)

        56 |   static int smear(int hashCode) {
        57 |     return C2 * Integer.rotateLeft(hashCode * C1, 15);
        58 |   }
        59 | 
        60 |   private boolean checkFilter(int c) {
>>>     61 |     return 1 == (1 & (filter >> c));
        62 |   }
        63 | 
        64 |   // This is all essentially copied from ImmutableSet, but we have to duplicate because
        65 |   // of dependencies.
        66 | 

Added src/com/google/common/io/LittleEndianDataInputStream.java (line 82)

        77 |   }
        78 | 
        79 |   @Override
        80 |   public int readUnsignedByte() throws IOException {
        81 |     int b1 = in.read();
>>>     82 |     if (0 > b1) {
        83 |       throw new EOFException();
        84 |     }
        85 |     
        86 |     return b1;
        87 |   }

Added src/com/google/common/io/LittleEndianDataInputStream.java (line 225)

       220 |    * @throws EOFException if the end of file (EOF) is encountered.
       221 |    */
       222 |   private byte readAndCheckByte() throws IOException, EOFException {
       223 |     int b1 = in.read();
       224 | 
>>>    225 |     if (-1 == b1) {
       226 |       throw new EOFException();
       227 |     }
       228 | 
       229 |     return (byte) b1;
       230 |   }

Added src/com/google/common/math/LongMath.java (line 234)

       229 |    * @throws IllegalArgumentException if {@code k < 0}
       230 |    */
       231 |   @GwtIncompatible("TODO")
       232 |   public static long pow(long b, int k) {
       233 |     checkNonNegative("exponent", k);
>>>    234 |     if (-2 <= b && b <= 2) {
       235 |       switch ((int) b) {
       236 |         case 0:
       237 |           return (k == 0) ? 1 : 0;
       238 |         case 1:
       239 |           return 1;

Added src/com/google/common/net/MediaType.java (line 628)

       623 |         tokenizer.consumeCharacter(';');
       624 |         tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE);
       625 |         String attribute = tokenizer.consumeToken(TOKEN_MATCHER);
       626 |         tokenizer.consumeCharacter('=');
       627 |         final String value;
>>>    628 |         if ('"' == tokenizer.previewChar()) {
       629 |           tokenizer.consumeCharacter('"');
       630 |           StringBuilder valueBuilder = new StringBuilder();
       631 |           while ('"' != tokenizer.previewChar()) {
       632 |             if ('\\' == tokenizer.previewChar()) {
       633 |               tokenizer.consumeCharacter('\\');

Added src/com/google/common/net/MediaType.java (line 631)

       626 |         tokenizer.consumeCharacter('=');
       627 |         final String value;
       628 |         if ('"' == tokenizer.previewChar()) {
       629 |           tokenizer.consumeCharacter('"');
       630 |           StringBuilder valueBuilder = new StringBuilder();
>>>    631 |           while ('"' != tokenizer.previewChar()) {
       632 |             if ('\\' == tokenizer.previewChar()) {
       633 |               tokenizer.consumeCharacter('\\');
       634 |               valueBuilder.append(tokenizer.consumeCharacter(ASCII));
       635 |             } else {
       636 |               valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER));

Added src/com/google/common/net/MediaType.java (line 632)

       627 |         final String value;
       628 |         if ('"' == tokenizer.previewChar()) {
       629 |           tokenizer.consumeCharacter('"');
       630 |           StringBuilder valueBuilder = new StringBuilder();
       631 |           while ('"' != tokenizer.previewChar()) {
>>>    632 |             if ('\\' == tokenizer.previewChar()) {
       633 |               tokenizer.consumeCharacter('\\');
       634 |               valueBuilder.append(tokenizer.consumeCharacter(ASCII));
       635 |             } else {
       636 |               valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER));
       637 |             }

romainbrenguier and others added 2 commits August 25, 2026 16:21
- Extract duplicated "float"/"double" string literals into constants (S1192)
- Fix isLiteral to handle unary minus/plus (e.g. -1 == index now detected)
- Use distinct message for relational Yoda conditions ("...and invert the operator")
- Remove BOOLEAN_LITERAL from Yoda detection (delegate to S1125)
- Remove transcendental Math functions from constant detection (results may vary across JVMs)
- Update ruling expectations and test samples accordingly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Generated with GitHub Actions
@github-actions

Copy link
Copy Markdown
Contributor

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

Please review and merge it into your branch.

- Remove inexact Math functions (cbrt, toDegrees, toRadians) from constant
  call detection as their results are not exactly specified across JVMs
- Fix HTML doc: remove boolean literal examples that conflict with S1125,
  correct rationale about Java assignment in conditional contexts
- Add test cases for newly excluded Math functions

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

gitar-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Reviewing your code

Code Review ✅ Approved 6 resolved / 6 findings

Implements rule S9360 to simplify constant expressions and comparisons, addressing issues with Yoda conditions, constant Math calls, compilation errors, and messaging. All findings were successfully resolved.

✅ 6 resolved
Bug: Compiling test sample does not compile: null == int

📄 java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java:64-69
Line 67 of the sample compares ((null)) with the int variable count; javac rejects this with "bad operand types for binary operator '=='" (verified locally), so the java-checks-test-sources/default module no longer compiles and the whole build breaks. Move the case to the non-compiling sample or compare against a reference-typed variable (the reported column range for null stays identical).

Bug: Yoda detection misses negative literals and flags constant-only comparisons

📄 java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java:105-112 📄 java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java:125-139
isLiteral (unlike isNumericLiteral used for Math arguments) does not skip UNARY_MINUS/UNARY_PLUS, so the very common Yoda form if (-1 == index) / if (-1 == s.indexOf("x")) produces no issue, while if (0 == -1) — where neither side is a variable — is reported with the message "Put the variable on the left side of this comparison" even though there is no variable to move. Reuse the unary-skipping logic in isLiteral so both operands are classified consistently.

Bug: Relational Yoda message suggests a fix that inverts the logic

📄 java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java:84-98 📄 java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java:71-85
For LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL_TO and GREATER_THAN_OR_EQUAL_TO the check emits the same message as for equality ("Put the variable on the left side of this comparison"), e.g. on if (0 < count). Literally following it yields if (count < 0), the opposite condition; the fix requires swapping operands and flipping the operator. Emit an operator-aware message for relational operators (or restrict the check to ==/!=), and update the sample expectations accordingly.

Quality: Constant Math detection double-reports with S2185 (both in Sonar way)

📄 java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java:35-49
ConstantMathCheck (S2185, also enabled in Sonar way) already reports Math.abs(<numeric literal>) and Math.sqrt/cbrt/log/log10/exp/asin/acos/atan/toDegrees(0.0|1.0), Math.sin/cos/tan/... (0.0); S9360 reports the same invocations with a different message, so users get two issues on the same line. Narrow S9360's matcher set (or coordinate with the RSPEC to deprecate the overlapping part of S2185) before enabling both by default.

Quality: Boolean-literal guidance conflicts with S1125 and with Java semantics

📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html:15-19 📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html:54-68 📄 java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java:138-148 📄 java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java:28-37
The rule reports true == flag and the HTML's compliant solution shows flag == true, which the default-active rule S1125 ("Boolean literals should not be redundant") flags — following S9360 introduces an S1125 issue. The HTML rationale also claims type systems "reject code that attempts assignment where a boolean expression is expected", which is false in Java for boolean variables (if (flag = true) compiles), so the stated justification does not hold for exactly the boolean case the rule reports. Drop BOOLEAN_LITERAL from the Yoda detection (leaving it to S1125) and fix the doc example/rationale.

...and 1 more resolved from earlier reviews

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

This PR implements rule S9360 for constant expressions and comparisons as specified in the objectives.

✅ 1 covered here
  • ✅ Implement rule S9360 for constant expressions and comparisons should be simplified
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

@github-actions

Copy link
Copy Markdown
Contributor

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

Please review and merge it into your branch.

Remove Math.log(2) detections from BloomFilter (method no longer matched),
add new negated literal Yoda conditions in LittleEndianDataInputStream and LongMath.

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 August 26, 2026 09:33

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

PR #6036 and PR #6022 are competing implementations of the same Jira deliverable, not complementary changes. SONARJAVA-6825 has one stated objective—create S9360—and no description or comments defining separate deliverables. Both PRs share the initial S9360 commits through 36d0e824, then diverge: #6022 implements the Yoda-condition subset, while this PR implements Yoda conditions plus constant Math calls. Both add the same rule key, check class, metadata, documentation, Sonar Way activation, and ruling files, so they cannot reasonably both merge. Please establish one canonical implementation before proceeding. If #6036 supersedes #6022, state that explicitly and close #6022; otherwise explain the intended split and re-scope the work so the two PRs no longer compete for S9360.

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