001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.strings;
005
006 import java.util.HashMap;
007 import java.util.HashSet;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011
012 import net.sourceforge.pmd.lang.ast.Node;
013 import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
014 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
015 import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
017 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
018 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
019 import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression;
020 import net.sourceforge.pmd.lang.java.ast.ASTName;
021 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
022 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
023 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
024 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
025 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
026 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
027 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
028 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
029 import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
030
031 /**
032 * This rule finds StringBuffers which may have been pre-sized incorrectly
033 *
034 * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
035 * @author Allan Caplan
036 */
037 public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule {
038
039 private final static Set<Class<? extends Node>> BLOCK_PARENTS;
040
041 static {
042 BLOCK_PARENTS = new HashSet<Class<? extends Node>>();
043 BLOCK_PARENTS.add(ASTIfStatement.class);
044 BLOCK_PARENTS.add(ASTSwitchStatement.class);
045 }
046
047 @Override
048 public Object visit(ASTVariableDeclaratorId node, Object data) {
049 if (!TypeHelper.isA(node.getNameDeclaration(), StringBuffer.class)) {
050 return data;
051 }
052 Node rootNode = node;
053 int anticipatedLength = 0;
054 int constructorLength = 16;
055
056 constructorLength = getConstructorLength(node, constructorLength);
057 anticipatedLength = getInitialLength(node);
058 List<NameOccurrence> usage = node.getUsages();
059 Map<Node, Map<Node, Integer>> blocks = new HashMap<Node, Map<Node, Integer>>();
060 for (int ix = 0; ix < usage.size(); ix++) {
061 NameOccurrence no = usage.get(ix);
062 Node n = no.getLocation();
063 if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
064
065 if (!no.isOnLeftHandSide() && !InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "setLength")) {
066 continue;
067 }
068 if (constructorLength != -1 && anticipatedLength > constructorLength) {
069 anticipatedLength += processBlocks(blocks);
070 String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
071 addViolation(data, rootNode, param);
072 }
073 constructorLength = getConstructorLength(n, constructorLength);
074 rootNode = n;
075 anticipatedLength = getInitialLength(node);
076 }
077 ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
078 int numChildren = s.jjtGetNumChildren();
079 for (int jx = 0; jx < numChildren; jx++) {
080 Node sn = s.jjtGetChild(jx);
081 if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
082 continue;
083 }
084 int thisSize = 0;
085 Node block = getFirstParentBlock(sn);
086 if (isAdditive(sn)) {
087 thisSize = processAdditive(sn);
088 } else {
089 thisSize = processNode(sn);
090 }
091 if (block != null) {
092 storeBlockStatistics(blocks, thisSize, block);
093 } else {
094 anticipatedLength += thisSize;
095 }
096 }
097 }
098 anticipatedLength += processBlocks(blocks);
099 if (constructorLength != -1 && anticipatedLength > constructorLength) {
100 String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
101 addViolation(data, rootNode, param);
102 }
103 return data;
104 }
105
106 /**
107 * This rule is concerned with IF and Switch blocks. Process the block into
108 * a local Map, from which we can later determine which is the longest block
109 * inside
110 *
111 * @param blocks
112 * The map of blocks in the method being investigated
113 * @param thisSize
114 * The size of the current block
115 * @param block
116 * The block in question
117 */
118 private void storeBlockStatistics(Map<Node, Map<Node, Integer>> blocks, int thisSize, Node block) {
119 Node statement = block.jjtGetParent();
120 if (block.jjtGetParent() instanceof ASTIfStatement) {
121 // Else Ifs are their own subnode in AST. So we have to
122 // look a little farther up the tree to find the IF statement
123 Node possibleStatement = statement.getFirstParentOfType(ASTIfStatement.class);
124 while (possibleStatement instanceof ASTIfStatement) {
125 statement = possibleStatement;
126 possibleStatement = possibleStatement.getFirstParentOfType(ASTIfStatement.class);
127 }
128 }
129 Map<Node, Integer> thisBranch = blocks.get(statement);
130 if (thisBranch == null) {
131 thisBranch = new HashMap<Node, Integer>();
132 blocks.put(statement, thisBranch);
133 }
134 Integer x = thisBranch.get(block);
135 if (x != null) {
136 thisSize += x;
137 }
138 thisBranch.put(statement, thisSize);
139 }
140
141 private int processBlocks(Map<Node, Map<Node, Integer>> blocks) {
142 int anticipatedLength = 0;
143 int ifLength = 0;
144 for (Map.Entry<Node, Map<Node, Integer>> entry: blocks.entrySet()) {
145 ifLength = 0;
146 for (Map.Entry<Node, Integer> entry2: entry.getValue().entrySet()) {
147 Integer value = entry2.getValue();
148 ifLength = Math.max(ifLength, value.intValue());
149 }
150 anticipatedLength += ifLength;
151 }
152 return anticipatedLength;
153 }
154
155 private int processAdditive(Node sn) {
156 ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
157 if (additive == null) {
158 return 0;
159 }
160 int anticipatedLength = 0;
161 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
162 Node childNode = additive.jjtGetChild(ix);
163 ASTLiteral literal = childNode.getFirstDescendantOfType(ASTLiteral.class);
164 if (literal != null && literal.getImage() != null) {
165 anticipatedLength += literal.getImage().length() - 2;
166 }
167 }
168
169 return anticipatedLength;
170 }
171
172 private static final boolean isLiteral(String str) {
173 if (str.length() == 0) {
174 return false;
175 }
176 char c = str.charAt(0);
177 return c == '"' || c == '\'';
178 }
179
180 private int processNode(Node sn) {
181 int anticipatedLength = 0;
182 ASTPrimaryPrefix xn = sn.getFirstDescendantOfType(ASTPrimaryPrefix.class);
183 if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0) instanceof ASTLiteral) {
184 String str = xn.jjtGetChild(0).getImage();
185 if (str != null) {
186 if(isLiteral(str)){
187 anticipatedLength += str.length() - 2;
188 } else if(str.startsWith("0x")){
189 anticipatedLength += 1;
190 } else {
191 anticipatedLength += str.length();
192 }
193 }
194 }
195 return anticipatedLength;
196 }
197
198 private int getConstructorLength(Node node, int constructorLength) {
199 int iConstructorLength = constructorLength;
200 Node block = node.getFirstParentOfType(ASTBlockStatement.class);
201 List<ASTLiteral> literal;
202
203 if (block == null) {
204 block = node.getFirstParentOfType(ASTFieldDeclaration.class);
205 }
206 if (block == null) {
207 block = node.getFirstParentOfType(ASTFormalParameter.class);
208 if (block != null) {
209 iConstructorLength = -1;
210 }
211 }
212
213 //if there is any addition/subtraction going on then just use the default.
214 ASTAdditiveExpression exp = block.getFirstDescendantOfType(ASTAdditiveExpression.class);
215 if(exp != null){
216 return 16;
217 }
218 ASTMultiplicativeExpression mult = block.getFirstDescendantOfType(ASTMultiplicativeExpression.class);
219 if(mult != null){
220 return 16;
221 }
222
223 literal = block.findDescendantsOfType(ASTLiteral.class);
224 if (literal.isEmpty()) {
225 List<ASTName> name = block.findDescendantsOfType(ASTName.class);
226 if (!name.isEmpty()) {
227 iConstructorLength = -1;
228 }
229 } else if (literal.size() == 1) {
230 String str = literal.get(0).getImage();
231 if (str == null) {
232 iConstructorLength = 0;
233 } else if (isLiteral(str)) {
234 // since it's not taken into account
235 // anywhere. only count the extra 16
236 // characters
237 iConstructorLength = 14 + str.length(); // don't add the constructor's length,
238 } else {
239 iConstructorLength = Integer.parseInt(str);
240 }
241 } else {
242 iConstructorLength = -1;
243 }
244
245 if (iConstructorLength == 0) {
246 if (constructorLength == -1) {
247 iConstructorLength = 16;
248 } else {
249 iConstructorLength = constructorLength;
250 }
251 }
252
253 return iConstructorLength;
254 }
255
256
257 private int getInitialLength(Node node) {
258 Node block = node.getFirstParentOfType(ASTBlockStatement.class);
259
260 if (block == null) {
261 block = node.getFirstParentOfType(ASTFieldDeclaration.class);
262 if (block == null) {
263 block = node.getFirstParentOfType(ASTFormalParameter.class);
264 }
265 }
266 List<ASTLiteral> literal = block.findDescendantsOfType(ASTLiteral.class);
267 if (literal.size() == 1) {
268 String str = literal.get(0).getImage();
269 if (str != null && isLiteral(str)) {
270 return str.length() - 2; // take off the quotes
271 }
272 }
273
274 return 0;
275 }
276
277 private boolean isAdditive(Node n) {
278 return n.hasDescendantOfType(ASTAdditiveExpression.class);
279 }
280
281 /**
282 * Locate the block that the given node is in, if any
283 *
284 * @param node
285 * The node we're looking for a parent of
286 * @return Node - The node that corresponds to any block that may be a
287 * parent of this object
288 */
289 private Node getFirstParentBlock(Node node) {
290 Node parentNode = node.jjtGetParent();
291
292 Node lastNode = node;
293 while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
294 lastNode = parentNode;
295 parentNode = parentNode.jjtGetParent();
296 }
297 if (parentNode instanceof ASTIfStatement) {
298 parentNode = lastNode;
299 } else if (parentNode instanceof ASTSwitchStatement) {
300 parentNode = getSwitchParent(parentNode, lastNode);
301 }
302 return parentNode;
303 }
304
305 /**
306 * Determine which SwitchLabel we belong to inside a switch
307 *
308 * @param parentNode
309 * The parent node we're looking at
310 * @param lastNode
311 * The last node processed
312 * @return The parent node for the switch statement
313 */
314 private static Node getSwitchParent(Node parentNode, Node lastNode) {
315 int allChildren = parentNode.jjtGetNumChildren();
316 ASTSwitchLabel label = null;
317 for (int ix = 0; ix < allChildren; ix++) {
318 Node n = parentNode.jjtGetChild(ix);
319 if (n instanceof ASTSwitchLabel) {
320 label = (ASTSwitchLabel) n;
321 } else if (n.equals(lastNode)) {
322 parentNode = label;
323 break;
324 }
325 }
326 return parentNode;
327 }
328
329 }
|