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