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