Repository navigation
Expand file tree
/
Copy pathShiftOnIntOrLongCheck.java
More file actions
177 lines (154 loc) · 6.55 KB
/
Copy pathShiftOnIntOrLongCheck.java
File metadata and controls
177 lines (154 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* 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.sonar.check.Rule;
import org.sonar.java.model.LineUtils;
import org.sonar.java.model.LiteralUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.location.Position;
import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree;
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.IdentifierTree;
import org.sonar.plugins.java.api.tree.SyntaxToken;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Rule(key = "S2183")
public class ShiftOnIntOrLongCheck extends IssuableSubscriptionVisitor {
private List<Tree> shiftTrees = new ArrayList<>();
@Override
public List<Kind> nodesToVisit() {
return Arrays.asList(Kind.LEFT_SHIFT, Kind.LEFT_SHIFT_ASSIGNMENT, Kind.RIGHT_SHIFT, Kind.RIGHT_SHIFT_ASSIGNMENT);
}
@Override
public void setContext(JavaFileScannerContext context) {
shiftTrees.clear();
super.setContext(context);
}
@Override
public void leaveFile(JavaFileScannerContext context) {
try {
for (int i = 0; i < shiftTrees.size(); i++) {
checkShiftTree(shiftTrees.get(i), i);
}
} finally {
shiftTrees.clear();
}
}
@Override
public void visitNode(Tree tree) {
shiftTrees.add(tree);
}
private void checkShiftTree(Tree tree, int treeIndex) {
String identifier;
ExpressionTree shift;
SyntaxToken operatorToken;
if (tree.is(Kind.LEFT_SHIFT, Kind.RIGHT_SHIFT)) {
BinaryExpressionTree binaryExpressionTree = (BinaryExpressionTree) tree;
if (isZeroMaskShift(binaryExpressionTree)) {
// No issue should be reported for "1 << 0" or "1 >> 0"
return;
}
identifier = getIdentifierName(binaryExpressionTree.leftOperand());
shift = binaryExpressionTree.rightOperand();
operatorToken = binaryExpressionTree.operatorToken();
} else {
AssignmentExpressionTree assignmentExpressionTree = (AssignmentExpressionTree) tree;
identifier = getIdentifierName(assignmentExpressionTree.variable());
shift = assignmentExpressionTree.expression();
operatorToken = assignmentExpressionTree.operatorToken();
}
checkShift((ExpressionTree) tree, shift, identifier, operatorToken, treeIndex);
}
private void checkShift(ExpressionTree tree, ExpressionTree shift, @Nullable String identifier, SyntaxToken operatorToken,
int treeIndex) {
Long literalValue = LiteralUtils.longLiteralValue(shift);
if (literalValue != null) {
int numericalBase = getNumericalBase(tree);
long reducedNumberBits = literalValue % numericalBase;
if (isInvalidShift(reducedNumberBits, literalValue, numericalBase, operatorToken, treeIndex)) {
reportIssue(operatorToken, getMessage(reducedNumberBits, numericalBase, identifier));
}
}
}
private boolean isInvalidShift(long reducedNumberBits, long numberBits, int base, SyntaxToken operatorToken, int treeIndex) {
return (reducedNumberBits == 0L && !aligned(operatorToken, treeIndex)) || tooManyBits(numberBits, base);
}
private boolean aligned(SyntaxToken operatorToken, int treeIndex) {
return (treeIndex > 0 && isAlignedWith(operatorToken, shiftTrees.get(treeIndex - 1)))
|| (treeIndex + 1 < shiftTrees.size() && isAlignedWith(operatorToken, shiftTrees.get(treeIndex + 1)));
}
private static boolean isAlignedWith(SyntaxToken operatorToken, Tree other) {
SyntaxToken otherOperator = operatorToken(other);
return otherOperator.text().equals(operatorToken.text())
&& Position.startOf(operatorToken).column() == Position.startOf(otherOperator).column()
// less than 2 lines distance
&& Math.abs(LineUtils.startLine(operatorToken) - LineUtils.startLine(otherOperator)) < 2;
}
private static SyntaxToken operatorToken(Tree tree) {
if (tree instanceof BinaryExpressionTree binaryExpressionTree) {
return binaryExpressionTree.operatorToken();
}
return ((AssignmentExpressionTree) tree).operatorToken();
}
private static boolean isZeroMaskShift(BinaryExpressionTree binaryExpressionTree) {
return isLiteralValue(binaryExpressionTree.leftOperand(), 1L) && isLiteralValue(binaryExpressionTree.rightOperand(), 0L);
}
private static boolean isLiteralValue(ExpressionTree tree, long value) {
Long evaluatedValue = LiteralUtils.longLiteralValue(tree);
return evaluatedValue != null && evaluatedValue == value;
}
private static String getMessage(long reducedNumberBits, int base, @Nullable String identifier) {
if (reducedNumberBits == 0L) {
return "Remove this useless shift";
} else if (base == 32) {
return MessageFormat.format(
identifier == null ?
"Either use a \"long\" or correct this shift to {0}" :
"Either make \"{1}\" a \"long\" or correct this shift to {0}",
reducedNumberBits, identifier);
} else {
return MessageFormat.format("Correct this shift to {0}", reducedNumberBits);
}
}
private static int getNumericalBase(ExpressionTree tree) {
if (tree.symbolType().is("int")) {
return 32;
}
return 64;
}
private static boolean tooManyBits(long numberBits, int base) {
return Math.abs(numberBits) >= base;
}
@CheckForNull
private static String getIdentifierName(ExpressionTree tree) {
if (tree.is(Kind.ARRAY_ACCESS_EXPRESSION)) {
return getIdentifierName(((ArrayAccessExpressionTree) tree).expression());
} else if (tree.is(Kind.IDENTIFIER)) {
return ((IdentifierTree) tree).name();
}
return null;
}
}