View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.strings;
5   
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import net.sourceforge.pmd.lang.ast.Node;
13  import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
14  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
15  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
16  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
17  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
19  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
20  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
21  import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression;
22  import net.sourceforge.pmd.lang.java.ast.ASTName;
23  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
24  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
25  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
26  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
27  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
28  import net.sourceforge.pmd.lang.java.ast.ASTType;
29  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
30  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
31  import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
32  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
33  import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
34  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
35  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
36  
37  /**
38   * This rule finds StringBuffers which may have been pre-sized incorrectly
39   *
40   * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
41   * @author Allan Caplan
42   */
43  public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule {
44  
45      private final static Set<Class<? extends Node>> BLOCK_PARENTS;
46  
47      static {
48          BLOCK_PARENTS = new HashSet<>(2);
49          BLOCK_PARENTS.add(ASTIfStatement.class);
50          BLOCK_PARENTS.add(ASTSwitchStatement.class);
51      }
52  
53      public static final int DEFAULT_BUFFER_SIZE = 16;	// as specified in StringBuffer & StringBuilder
54  
55      @Override
56      public Object visit(ASTVariableDeclaratorId node, Object data) {
57          if (!TypeHelper.isEither(node.getNameDeclaration(), StringBuffer.class, StringBuilder.class)) {
58              return data;
59          }
60          Node rootNode = node;
61          int anticipatedLength = 0;
62          int constructorLength = DEFAULT_BUFFER_SIZE;
63  
64          constructorLength = getConstructorLength(node, constructorLength);
65          anticipatedLength = getInitialLength(node);
66  
67          anticipatedLength += getConstructorAppendsLength(node);
68  
69          List<NameOccurrence> usage = node.getUsages();
70          Map<Node, Map<Node, Integer>> blocks = new HashMap<>();
71          for (NameOccurrence no : usage) {
72              JavaNameOccurrence jno = (JavaNameOccurrence)no;
73              Node n = jno.getLocation();
74              if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
75  
76                  if (!jno.isOnLeftHandSide() && !InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "setLength")) {
77                      continue;
78                  }
79                  if (constructorLength != -1 && anticipatedLength > constructorLength) {
80                      anticipatedLength += processBlocks(blocks);
81                      String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
82                      addViolation(data, rootNode, param);
83                  }
84                  constructorLength = getConstructorLength(n, constructorLength);
85                  rootNode = n;
86                  anticipatedLength = getInitialLength(node);
87              }
88              ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
89              int numChildren = s.jjtGetNumChildren();
90              for (int jx = 0; jx < numChildren; jx++) {
91              	Node sn = s.jjtGetChild(jx);
92                  if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
93                      continue;
94                  }
95                  int thisSize = 0;
96                  Node block = getFirstParentBlock(sn);
97                  if (isAdditive(sn)) {
98                      thisSize = processAdditive(sn);
99                  } else {
100                     thisSize = processNode(sn);
101                 }
102                 if (block != null) {
103                     storeBlockStatistics(blocks, thisSize, block);
104                 } else {
105                     anticipatedLength += thisSize;
106                 }
107             }
108         }
109         anticipatedLength += processBlocks(blocks);
110         if (constructorLength != -1 && anticipatedLength > constructorLength) {
111             String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
112             addViolation(data, rootNode, param);
113         }
114         return data;
115     }
116 
117     /**
118      * This rule is concerned with IF and Switch blocks. Process the block into
119      * a local Map, from which we can later determine which is the longest block
120      * inside
121      *
122      * @param blocks
123      *            The map of blocks in the method being investigated
124      * @param thisSize
125      *            The size of the current block
126      * @param block
127      *            The block in question
128      */
129     private void storeBlockStatistics(Map<Node, Map<Node, Integer>> blocks, int thisSize, Node block) {
130         Node statement = block.jjtGetParent();
131         if (block.jjtGetParent() instanceof ASTIfStatement) {
132             // Else Ifs are their own subnode in AST. So we have to
133             // look a little farther up the tree to find the IF statement
134             Node possibleStatement = statement.getFirstParentOfType(ASTIfStatement.class);
135             while (possibleStatement instanceof ASTIfStatement) {
136                 statement = possibleStatement;
137                 possibleStatement = possibleStatement.getFirstParentOfType(ASTIfStatement.class);
138             }
139         }
140         Map<Node, Integer> thisBranch = blocks.get(statement);
141         if (thisBranch == null) {
142             thisBranch = new HashMap<>();
143             blocks.put(statement, thisBranch);
144         }
145         Integer x = thisBranch.get(block);
146         if (x != null) {
147             thisSize += x;
148         }
149         thisBranch.put(statement, thisSize);
150     }
151 
152     private int processBlocks(Map<Node, Map<Node, Integer>> blocks) {
153         int anticipatedLength = 0;
154         int ifLength = 0;
155         for (Map.Entry<Node, Map<Node, Integer>> entry: blocks.entrySet()) {
156             ifLength = 0;
157             for (Map.Entry<Node, Integer> entry2: entry.getValue().entrySet()) {
158                 Integer value = entry2.getValue();
159                 ifLength = Math.max(ifLength, value.intValue());
160             }
161             anticipatedLength += ifLength;
162         }
163         return anticipatedLength;
164     }
165 
166     private int processAdditive(Node sn) {
167         ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
168         if (additive == null) {
169             return 0;
170         }
171         int anticipatedLength = 0;
172         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
173             Node childNode = additive.jjtGetChild(ix);
174             ASTLiteral literal = childNode.getFirstDescendantOfType(ASTLiteral.class);
175             if (literal != null && literal.getImage() != null) {
176                 anticipatedLength += literal.getImage().length() - 2;
177             }
178         }
179 
180         return anticipatedLength;
181     }
182 
183     private static final boolean isStringOrCharLiteral(ASTLiteral literal) {
184     	return literal.isStringLiteral() || literal.isCharLiteral();
185     }
186 
187     private int processNode(Node sn) {
188         int anticipatedLength = 0;
189         if ( sn != null ) {
190             ASTPrimaryPrefix xn = sn.getFirstDescendantOfType(ASTPrimaryPrefix.class);
191             if ( xn != null ) {
192                 if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0) instanceof ASTLiteral) {
193                 	ASTLiteral literal = (ASTLiteral) xn.jjtGetChild(0);
194                     String str = xn.jjtGetChild(0).getImage();
195                     if (str != null) {
196                         if (literal.isStringLiteral()) {
197                             anticipatedLength += str.length() - 2;
198                         } else if (literal.isCharLiteral()) {
199                             anticipatedLength += 1;
200 	                    } else if(literal.isIntLiteral() && str.startsWith("0x")){
201 	                    	// but only if we are not inside a cast expression
202 	                    	Node parentNode = literal.jjtGetParent().jjtGetParent().jjtGetParent();
203 							if (parentNode instanceof ASTCastExpression
204 	                    		&& parentNode.getFirstChildOfType(ASTType.class).getType() == char.class) {
205 	                    		anticipatedLength += 1;
206 	                    	} else {
207     	                    	// e.g. 0xdeadbeef -> will be converted to a base 10 integer string: 3735928559
208     	                    	anticipatedLength += String.valueOf(Long.parseLong(str.substring(2), 16)).length();
209 	                    	}
210 	                    } else {
211 	                        anticipatedLength += str.length();
212 	                    }
213                     }
214                 }
215             }
216         }
217         return anticipatedLength;
218     }
219 
220     private int getConstructorLength(Node node, int constructorLength) {
221         int iConstructorLength = constructorLength;
222         Node block = node.getFirstParentOfType(ASTBlockStatement.class);
223 
224         if (block == null) {
225             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
226         }
227         if (block == null) {
228             block = node.getFirstParentOfType(ASTFormalParameter.class);
229             if (block != null) {
230                 iConstructorLength = -1;
231             } else {
232             	return DEFAULT_BUFFER_SIZE;
233             }
234         }
235 
236         //if there is any addition/subtraction going on then just use the default.
237         ASTAdditiveExpression exp = block.getFirstDescendantOfType(ASTAdditiveExpression.class);
238         if (exp != null){
239             return DEFAULT_BUFFER_SIZE;
240         }
241         ASTMultiplicativeExpression mult = block.getFirstDescendantOfType(ASTMultiplicativeExpression.class);
242         if (mult != null){
243             return DEFAULT_BUFFER_SIZE;
244         }
245 
246         List<ASTLiteral> literals;
247         ASTAllocationExpression constructorCall = block.getFirstDescendantOfType(ASTAllocationExpression.class);
248         if (constructorCall != null) {
249             // if this is a constructor call, only consider the literals within it.
250             literals = constructorCall.findDescendantsOfType(ASTLiteral.class);
251         } else {
252             // otherwise it might be a setLength call...
253             literals = block.findDescendantsOfType(ASTLiteral.class);
254         }
255         if (literals.isEmpty()) {
256             List<ASTName> name = block.findDescendantsOfType(ASTName.class);
257             if (!name.isEmpty()) {
258                 iConstructorLength = -1;
259             }
260         } else if (literals.size() == 1) {
261         	ASTLiteral literal = literals.get(0);
262             String str = literal.getImage();
263             if (str == null) {
264                 iConstructorLength = 0;
265             } else if (isStringOrCharLiteral(literal)) {
266                 // since it's not taken into account
267                 // anywhere. only count the extra 16
268                 // characters
269                 iConstructorLength = 14 + str.length(); // don't add the constructor's length,
270             } else if (literal.isIntLiteral() && str.startsWith("0x")) {
271             	// bug 3516101 - the string could be a hex number
272             	iConstructorLength = Integer.parseInt(str.substring(2), 16);
273             } else {
274                 iConstructorLength = Integer.parseInt(str);
275             }
276         } else {
277             iConstructorLength = -1;
278         }
279 
280         if (iConstructorLength == 0) {
281             if (constructorLength == -1) {
282         	iConstructorLength = DEFAULT_BUFFER_SIZE;
283             } else {
284         	iConstructorLength = constructorLength;
285             }
286         }
287 
288         return iConstructorLength;
289     }
290 
291 
292     private int getInitialLength(Node node) {
293 
294     	Node block = node.getFirstParentOfType(ASTBlockStatement.class);
295 
296         if (block == null) {
297             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
298             if (block == null) {
299                 block = node.getFirstParentOfType(ASTFormalParameter.class);
300             }
301         }
302         List<ASTLiteral> literals = block.findDescendantsOfType(ASTLiteral.class);
303         if (literals.size() == 1) {
304         	ASTLiteral literal = literals.get(0);
305             String str = literal.getImage();
306             if (str != null && isStringOrCharLiteral(literal)) {
307                 return str.length() - 2; // take off the quotes
308             }
309         }
310 
311         return 0;
312     }
313 
314     private int getConstructorAppendsLength(final Node node) {
315       final Node parent = node.getFirstParentOfType(ASTVariableDeclarator.class);
316        int size = 0;
317        if (parent != null) {
318          final Node initializer = parent.getFirstChildOfType(ASTVariableInitializer.class);
319          if (initializer != null) {
320              final Node primExp = initializer.getFirstDescendantOfType(ASTPrimaryExpression.class);
321              if (primExp != null) {
322                  for (int i = 0; i < primExp.jjtGetNumChildren(); i++) {
323                    final Node sn = primExp.jjtGetChild(i);
324                    if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
325                      continue;
326                    }
327                    size += processNode(sn);
328                  }
329              }
330          }
331        }
332        return size;
333     }
334 
335     private boolean isAdditive(Node n) {
336         ASTAdditiveExpression add = n.getFirstDescendantOfType(ASTAdditiveExpression.class);
337         // if the first descendant additive expression is deeper than 4 levels,
338         // it belongs to a nested method call and not anymore to the append argument.
339         return add != null && add.getNthParent(4) == n;
340     }
341 
342     /**
343      * Locate the block that the given node is in, if any
344      *
345      * @param node
346      *            The node we're looking for a parent of
347      * @return Node - The node that corresponds to any block that may be a
348      *         parent of this object
349      */
350     private Node getFirstParentBlock(Node node) {
351         Node parentNode = node.jjtGetParent();
352 
353         Node lastNode = node;
354         while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
355             lastNode = parentNode;
356             parentNode = parentNode.jjtGetParent();
357         }
358         if (parentNode instanceof ASTIfStatement) {
359             parentNode = lastNode;
360         } else if (parentNode instanceof ASTSwitchStatement) {
361             parentNode = getSwitchParent(parentNode, lastNode);
362         }
363         return parentNode;
364     }
365 
366     /**
367      * Determine which SwitchLabel we belong to inside a switch
368      *
369      * @param parentNode
370      *            The parent node we're looking at
371      * @param lastNode
372      *            The last node processed
373      * @return The parent node for the switch statement
374      */
375     private static Node getSwitchParent(Node parentNode, Node lastNode) {
376         int allChildren = parentNode.jjtGetNumChildren();
377         ASTSwitchLabel label = null;
378         for (int ix = 0; ix < allChildren; ix++) {
379             Node n = parentNode.jjtGetChild(ix);
380             if (n instanceof ASTSwitchLabel) {
381                 label = (ASTSwitchLabel) n;
382             } else if (n.equals(lastNode)) {
383                 parentNode = label;
384                 break;
385             }
386         }
387         return parentNode;
388     }
389 
390 }