Skip to content

Commit c9d7e50

Browse files
authored
SONARJAVA-5754 : Increment and decrement operators (++/--) should not be used with floating point variables (#5371)
1 parent 07521ba commit c9d7e50

File tree

9 files changed

+294
-1
lines changed

9 files changed

+294
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ruleKey": "S8346",
3+
"hasTruePositives": true,
4+
"falseNegatives": 0,
5+
"falsePositives": 0
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [
3+
1136
4+
]
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java": [
3+
85
4+
]
5+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import java.util.List;
20+
import java.util.Optional;
21+
import org.sonar.check.Rule;
22+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
23+
import org.sonar.plugins.java.api.semantic.Type;
24+
import org.sonar.plugins.java.api.semantic.Type.Primitives;
25+
import org.sonar.plugins.java.api.tree.IdentifierTree;
26+
import org.sonar.plugins.java.api.tree.Tree;
27+
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
28+
29+
@Rule(key = "S8346")
30+
public class IncDecOnFloatingPointCheck extends IssuableSubscriptionVisitor {
31+
32+
@Override
33+
public List<Tree.Kind> nodesToVisit() {
34+
return List.of(
35+
Tree.Kind.POSTFIX_INCREMENT,
36+
Tree.Kind.PREFIX_INCREMENT,
37+
Tree.Kind.POSTFIX_DECREMENT,
38+
Tree.Kind.PREFIX_DECREMENT
39+
);
40+
}
41+
42+
@Override
43+
public void visitNode(Tree tree) {
44+
Optional.of(tree)
45+
.filter(UnaryExpressionTree.class::isInstance)
46+
.map(UnaryExpressionTree.class::cast)
47+
.filter(unaryExpr ->
48+
unaryExpr.expression() instanceof IdentifierTree identifierTree
49+
&& isFloatingPoint(identifierTree.symbolType())
50+
)
51+
.ifPresent(unaryExpr -> reportIssue(
52+
unaryExpr,
53+
"%s operator (%s) should not be used with floating point variables".formatted(
54+
isIncrement(unaryExpr) ? "Increment" : "Decrement",
55+
unaryExpr.operatorToken().text()
56+
)
57+
));
58+
}
59+
60+
61+
private static boolean isFloatingPoint(Type type) {
62+
return type.isPrimitive(Primitives.FLOAT) || type.isPrimitive(Primitives.DOUBLE);
63+
}
64+
65+
private static boolean isIncrement(UnaryExpressionTree unaryExp) {
66+
return unaryExp.is(
67+
Tree.Kind.PREFIX_INCREMENT,
68+
Tree.Kind.POSTFIX_INCREMENT
69+
);
70+
}
71+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
class A {
2+
void specNonCompliantExamples() {
3+
for (float i = 16_000_000; i < 17_000_000; i++) { // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
4+
// ^^^
5+
// ...
6+
}
7+
8+
9+
float x = 0f;
10+
double y = 1.0;
11+
12+
x++; // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
13+
// ^^^
14+
y--; // Noncompliant {{Decrement operator (--) should not be used with floating point variables}}
15+
// ^^^
16+
}
17+
18+
void specCompliantExamples() {
19+
for (int i = 16_000_000; i < 17_000_000; i++) { // Compliant
20+
// ...
21+
}
22+
23+
float x = 0f;
24+
double y = 1.0;
25+
26+
x += 1.0; // Compliant
27+
y -= 1.0; // Compliant
28+
}
29+
30+
void floatIsNotCompliant() {
31+
float y = 0.1f;
32+
y++; // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
33+
// ^^^
34+
++y; // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
35+
// ^^^
36+
y--; // Noncompliant {{Decrement operator (--) should not be used with floating point variables}}
37+
// ^^^
38+
--y; // Noncompliant {{Decrement operator (--) should not be used with floating point variables}}
39+
// ^^^
40+
}
41+
42+
void doubleIsNotCompliant() {
43+
double y = 0.1;
44+
y++; // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
45+
// ^^^
46+
++y; // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
47+
// ^^^
48+
y--; // Noncompliant {{Decrement operator (--) should not be used with floating point variables}}
49+
// ^^^
50+
--y; // Noncompliant {{Decrement operator (--) should not be used with floating point variables}}
51+
// ^^^
52+
}
53+
54+
void intIsCompliant() {
55+
int z = 0;
56+
z++; // Compliant
57+
++z; // Compliant
58+
z--; // Compliant
59+
--z; // Compliant
60+
}
61+
62+
void longIsCompliant() {
63+
long w = 0L;
64+
w++; // Compliant
65+
++w; // Compliant
66+
w--; // Compliant
67+
--w; // Compliant
68+
}
69+
70+
void charIsCompliant() {
71+
char c = 'a';
72+
c++; // Compliant
73+
++c; // Compliant
74+
c--; // Compliant
75+
--c; // Compliant
76+
}
77+
78+
void otherOperatorsNotAffected() {
79+
float f = 1f;
80+
// test unary operator -
81+
float g = -f; // compliant
82+
// test binary operators just in case
83+
float h = f + 1f; // compliant
84+
}
85+
86+
// variable shadowing test
87+
private double d = 3.4;
88+
89+
void variableShadowingTestCompliant() {
90+
for (int d = 0; d < 10; d++) { // Compliant
91+
}
92+
}
93+
// variable shadowing test
94+
private int i = 3;
95+
96+
void variableShadowingTestCompliant() {
97+
for (float i = 0; i < 10; i++) { // Noncompliant {{Increment operator (++) should not be used with floating point variables}}
98+
}
99+
}
100+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.sonar.java.checks.verifier.CheckVerifier;
21+
22+
class IncDecOnFloatingPointCheckTest {
23+
24+
@Test
25+
void test() {
26+
CheckVerifier.newVerifier()
27+
.onFile("src/test/files/checks/IncDecOnFloatingPointCheck.java")
28+
.withCheck(new IncDecOnFloatingPointCheck())
29+
.verifyIssues();
30+
}
31+
32+
@Test
33+
void testNoSemantic() {
34+
CheckVerifier.newVerifier()
35+
.withoutSemantic()
36+
.onFile("src/test/files/checks/IncDecOnFloatingPointCheck.java")
37+
.withCheck(new IncDecOnFloatingPointCheck())
38+
.verifyIssues();
39+
}
40+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<p>This rule raises an issue when increment (<code>++</code>) or decrement (<code>--</code>) operators are used with floating-point variables.</p>
2+
<h2>Why is this an issue?</h2>
3+
<p>Increment and decrement operators (<code>++</code> and <code>--</code>) shouldn’t be used with floating-point variables (like <code>float</code> or
4+
<code>double</code>). While the language allows it, the usage is not idiomatic, and most developers intuitively expect <code>x++</code> to apply to
5+
integer types. Using it on a float violates this common expectation and can lead to misleading code.</p>
6+
<h3>What is the potential impact?</h3>
7+
<p>Floating-point arithmetic has some non-intuitive properties, which can lead to unexpected bugs. For example, the following loop will not terminate.
8+
This happens because <code>float</code> has only 24 bits of precision (mantissa) and once a number gets large enough, adding <code>1.0</code> becomes
9+
insignificant and the increment operation does nothing:</p>
10+
<pre>
11+
for (float x = 16_000_000; x &lt; 17_000_000; x++) {
12+
// ...
13+
}
14+
// The loop does not terminate
15+
</pre>
16+
<p>The problem would not occur if <code>int</code> is used instead of <code>float</code>, even though both types occupy 32 bits:</p>
17+
<pre>
18+
for (int x = 16_000_000; x &lt; 17_000_000; x++) {
19+
// ...
20+
}
21+
// This loop terminates.
22+
</pre>
23+
<h2>How to fix it</h2>
24+
<p>Using the compound assignment operators (<code>+=</code> and <code>-=</code>) makes the intent clearer and avoids the surprising use of
25+
<code>++</code> and <code>--</code> on floating-point types.</p>
26+
<h3>Code examples</h3>
27+
<h4>Noncompliant code example</h4>
28+
<pre>
29+
float x = 0f;
30+
double y = 1.0;
31+
32+
x++;
33+
y--;
34+
</pre>
35+
<h4>Compliant solution</h4>
36+
<pre>
37+
float x = 0f;
38+
double y = 1.0;
39+
40+
x += 1.0;
41+
y -= 1.0;
42+
</pre>
43+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"title": "Increment and decrement operators (++\/--) should not be used with floating point variables",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [],
10+
"defaultSeverity": "Major",
11+
"ruleSpecification": "RSPEC-8346",
12+
"sqKey": "S8346",
13+
"scope": "All",
14+
"quickfix": "targeted",
15+
"code": {
16+
"impacts": {
17+
"MAINTAINABILITY": "HIGH",
18+
"RELIABILITY": "MEDIUM"
19+
},
20+
"attribute": "CONVENTIONAL"
21+
}
22+
}

sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@
516516
"S7479",
517517
"S7481",
518518
"S7482",
519-
"S7629"
519+
"S7629",
520+
"S8346"
520521
]
521522
}

0 commit comments

Comments
 (0)