From c9f3305f4b633393272f3c39f4d70f18b4e6e146 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 18 Aug 2026 11:13:31 +0200 Subject: [PATCH 1/3] Implement new rule S9344 Detect bitwise AND operations with literal zero (`& 0` and `&= 0`), which always produce zero regardless of the other operand, indicating a likely programming error such as a wrong constant or wrong operator. --- .../checks/BitwiseAndWithZeroCheckSample.java | 74 +++++++++++++++++++ .../java/checks/BitwiseAndWithZeroCheck.java | 60 +++++++++++++++ .../checks/BitwiseAndWithZeroCheckTest.java | 43 +++++++++++ .../org/sonar/l10n/java/rules/java/S9344.html | 20 +++++ .../org/sonar/l10n/java/rules/java/S9344.json | 23 ++++++ .../resources/profiles/Sonar_agentic_AI/S9344 | 0 .../main/resources/profiles/Sonar_way/S9344 | 0 7 files changed, 220 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/BitwiseAndWithZeroCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.json create mode 100644 sonar-java-plugin/src/main/resources/profiles/Sonar_agentic_AI/S9344 create mode 100644 sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9344 diff --git a/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java new file mode 100644 index 00000000000..6abb4691608 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java @@ -0,0 +1,74 @@ +package checks; + +class BitwiseAndWithZeroCheckSample { + + private static final int READ_PERMISSION = 0x04; + + int getFlags() { + return 42; + } + + void noncompliantPatterns() { + int flags = getFlags(); + int result; + + // Basic cases + result = flags & 0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = 0 & flags; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Hex zero + result = flags & 0x0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = flags & 0x00; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = flags & 0X0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Long zero + result = (int) (flags & 0L); // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = (int) (flags & 0x00L); // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Binary zero + result = flags & 0b0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = flags & 0B0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Octal zero (leading zero) + result = flags & 00; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Compound assignment + flags &= 0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + flags &= 0x0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + flags &= 0L; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + + // Nested in comparison (issue on the & expression) + if ((flags & 0) == 0) { } // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + if ((flags & 0) != 0) { } // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + } + + void compliantPatterns() { + int flags = getFlags(); + int mask = 0xFF; + int result; + + // Non-zero bitmask + result = flags & 0x01; + result = flags & 0xFF; + result = flags & 1; + + // Variable operands + result = flags & mask; + result = flags & READ_PERMISSION; + + // Two variables + int a = 1, b = 2; + result = a & b; + + // Non-zero compound assignment + flags &= 0x0F; + + // Different operators (covered by S2437) + result = flags | 0; + result = flags ^ 0; + + // Long non-zero bitmask + long longResult = flags & 0xFFL; + longResult = flags & 0x0FL; + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java b/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java new file mode 100644 index 00000000000..ffacb239ab8 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java @@ -0,0 +1,60 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.Arrays; +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.java.model.LiteralUtils; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.BinaryExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.Tree.Kind; + +@Rule(key = "S9344") +public class BitwiseAndWithZeroCheck extends IssuableSubscriptionVisitor { + + private static final String MESSAGE = "Remove this bitwise AND with zero; the result is always zero."; + + @Override + public List nodesToVisit() { + return Arrays.asList(Kind.AND, Kind.AND_ASSIGNMENT); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Kind.AND)) { + BinaryExpressionTree binary = (BinaryExpressionTree) tree; + if (isZero(binary.leftOperand()) || isZero(binary.rightOperand())) { + reportIssue(tree, MESSAGE); + } + } else { + AssignmentExpressionTree assignment = (AssignmentExpressionTree) tree; + if (isZero(assignment.expression())) { + reportIssue(assignment.operatorToken(), MESSAGE); + } + } + } + + private static boolean isZero(ExpressionTree expression) { + Long value = LiteralUtils.longLiteralValue(expression); + return value != null && value == 0L; + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/BitwiseAndWithZeroCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/BitwiseAndWithZeroCheckTest.java new file mode 100644 index 00000000000..38d7bd03212 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/BitwiseAndWithZeroCheckTest.java @@ -0,0 +1,43 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class BitwiseAndWithZeroCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/BitwiseAndWithZeroCheckSample.java")) + .withCheck(new BitwiseAndWithZeroCheck()) + .verifyIssues(); + } + + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/BitwiseAndWithZeroCheckSample.java")) + .withCheck(new BitwiseAndWithZeroCheck()) + .withoutSemantic() + .verifyIssues(); + } + +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.html new file mode 100644 index 00000000000..6329aefa83d --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.html @@ -0,0 +1,20 @@ +

Why is this an issue?

+

A bitwise AND operation combines two values bit by bit. When one of the operands is 0, every bit in the result will be 0 +because 0 AND anything is always 0. This makes the operation meaningless and any subsequent comparison trivial.

+

This pattern almost always indicates a programming error, such as using the wrong constant, the wrong operator, or a copy-paste mistake.

+

How to fix it

+

Replace the 0 with the intended bitmask constant.

+

Noncompliant code example

+
+int flags = getFlags();
+if ((flags & 0) == 0) { // Noncompliant - always true
+    doSomething();
+}
+
+

Compliant solution

+
+int flags = getFlags();
+if ((flags & 0x01) == 0) { // Compliant - checks if the least significant bit is not set
+    doSomething();
+}
+
diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.json new file mode 100644 index 00000000000..a6b7607c7d5 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9344.json @@ -0,0 +1,23 @@ +{ + "title": "Bitwise AND operations with zero should be corrected", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "suspicious" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-9344", + "sqKey": "S9344", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "RELIABILITY": "HIGH" + }, + "attribute": "LOGICAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_agentic_AI/S9344 b/sonar-java-plugin/src/main/resources/profiles/Sonar_agentic_AI/S9344 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9344 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9344 new file mode 100644 index 00000000000..e69de29bb2d From 0955b343db385c706e3dc9f17db33bb658f2ed1a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 19 Aug 2026 10:10:33 +0200 Subject: [PATCH 2/3] Fix agentic profile test: update expected rule count to 466 S9344 adds one rule to the quality profile, incrementing the total from 465 to 466. Co-Authored-By: Claude Opus 4.6 --- .../java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java index 705a056568f..738427fa588 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -60,7 +60,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(465) + .hasSize(466) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101", From 4f9d7b21edd73f015cf54ea83ef9d8d9c9e23c45 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 19 Aug 2026 15:19:20 +0200 Subject: [PATCH 3/3] Address review feedback: handle parenthesized zero and fix issue location - Wrap expression with ExpressionUtils.skipParentheses() in isZero() to detect parenthesized zero literals like `flags & (0)` - Report issue on operator token instead of whole expression for binary `&` to be consistent with `&=` case and sibling rule S2437 - Add test cases for parenthesized zero (compliant and noncompliant) Co-Authored-By: Claude Opus 4.6 --- .../main/java/checks/BitwiseAndWithZeroCheckSample.java | 8 ++++++++ .../org/sonar/java/checks/BitwiseAndWithZeroCheck.java | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java index 6abb4691608..cd5d05ffd04 100644 --- a/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/BitwiseAndWithZeroCheckSample.java @@ -37,6 +37,11 @@ void noncompliantPatterns() { flags &= 0x0; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} flags &= 0L; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + // Parenthesized zero + result = flags & (0); // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + result = (0) & flags; // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + flags &= (0); // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} + // Nested in comparison (issue on the & expression) if ((flags & 0) == 0) { } // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} if ((flags & 0) != 0) { } // Noncompliant {{Remove this bitwise AND with zero; the result is always zero.}} @@ -67,6 +72,9 @@ void compliantPatterns() { result = flags | 0; result = flags ^ 0; + // Parenthesized non-zero + result = flags & (0x0F); + // Long non-zero bitmask long longResult = flags & 0xFFL; longResult = flags & 0x0FL; diff --git a/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java b/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java index ffacb239ab8..c05a89e1a16 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/BitwiseAndWithZeroCheck.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.List; import org.sonar.check.Rule; +import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.LiteralUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; @@ -42,7 +43,7 @@ public void visitNode(Tree tree) { if (tree.is(Kind.AND)) { BinaryExpressionTree binary = (BinaryExpressionTree) tree; if (isZero(binary.leftOperand()) || isZero(binary.rightOperand())) { - reportIssue(tree, MESSAGE); + reportIssue(binary.operatorToken(), MESSAGE); } } else { AssignmentExpressionTree assignment = (AssignmentExpressionTree) tree; @@ -53,7 +54,7 @@ public void visitNode(Tree tree) { } private static boolean isZero(ExpressionTree expression) { - Long value = LiteralUtils.longLiteralValue(expression); + Long value = LiteralUtils.longLiteralValue(ExpressionUtils.skipParentheses(expression)); return value != null && value == 0L; }