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