View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.strings;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTAdditiveExpression;
8   import net.sourceforge.pmd.ast.ASTBlockStatement;
9   import net.sourceforge.pmd.ast.ASTCastExpression;
10  import net.sourceforge.pmd.ast.ASTFieldDeclaration;
11  import net.sourceforge.pmd.ast.ASTFormalParameter;
12  import net.sourceforge.pmd.ast.ASTIfStatement;
13  import net.sourceforge.pmd.ast.ASTLiteral;
14  import net.sourceforge.pmd.ast.ASTName;
15  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
16  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
17  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
18  import net.sourceforge.pmd.ast.ASTSwitchLabel;
19  import net.sourceforge.pmd.ast.ASTSwitchStatement;
20  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
21  import net.sourceforge.pmd.ast.Node;
22  import net.sourceforge.pmd.ast.SimpleNode;
23  import net.sourceforge.pmd.symboltable.NameOccurrence;
24  
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.oro.text.perl.Perl5Util;
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 InsufficientStringBufferDeclaration extends AbstractRule {
41  
42      private final static Set blockParents;
43  
44      static {
45          blockParents = new HashSet();
46          blockParents.add(ASTIfStatement.class);
47          blockParents.add(ASTSwitchStatement.class);
48      }
49  
50      private final Perl5Util regexp = new Perl5Util();
51  
52      public Object visit(ASTVariableDeclaratorId node, Object data) {
53  
54          if (!"StringBuffer".equals(node.getNameDeclaration().getTypeImage())) {
55              return data;
56          }
57          Node rootNode = node;
58          int anticipatedLength = 0;
59          int constructorLength = 16;
60  
61          constructorLength = getConstructorLength(node, constructorLength);
62          List usage = node.getUsages();
63          Map blocks = new HashMap();
64          for (int ix = 0; ix < usage.size(); ix++) {
65              NameOccurrence no = (NameOccurrence) usage.get(ix);
66              SimpleNode n = no.getLocation();
67              if (!InefficientStringBuffering.isInStringBufferAppend(n, 3)) {
68                  if (!no.isOnLeftHandSide()) {
69                      continue;
70                  }
71                  if (constructorLength != -1 && anticipatedLength > constructorLength) {
72                      anticipatedLength += processBlocks(blocks);
73                      String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
74                      addViolation(data, rootNode, param);
75                  }
76                  constructorLength = getConstructorLength(n, constructorLength);
77                  rootNode = n;
78                  anticipatedLength = 0;
79              }
80              ASTPrimaryExpression s = (ASTPrimaryExpression) n.getFirstParentOfType(ASTPrimaryExpression.class);
81              int numChildren = s.jjtGetNumChildren();
82              for (int jx = 0; jx < numChildren; jx++) {
83                  SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
84                  if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
85                      continue;
86                  }
87                  int thisSize = 0;
88                  Node block = getFirstParentBlock(sn);
89                  if (isAdditive(sn)) {
90                      thisSize = processAdditive(sn);
91                  } else {
92                      thisSize = processNode(sn);
93                  }
94                  if (block != null) {
95                      storeBlockStatistics(blocks, thisSize, block);
96                  } else {
97                      anticipatedLength += thisSize;
98                  }
99              }
100         }
101         anticipatedLength += processBlocks(blocks);
102         if (constructorLength != -1 && anticipatedLength > constructorLength) {
103             String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
104             addViolation(data, rootNode, param);
105         }
106         return data;
107     }
108 
109     /***
110      * This rule is concerned with IF and Switch blocks. Process the block into
111      * a local Map, from which we can later determine which is the longest block
112      * inside
113      * 
114      * @param blocks
115      *            The map of blocks in the method being investigated
116      * @param thisSize
117      *            The size of the current block
118      * @param block
119      *            The block in question
120      */
121     private void storeBlockStatistics(Map blocks, int thisSize, Node block) {
122         Node statement = block.jjtGetParent();
123         if (ASTIfStatement.class.equals(block.jjtGetParent().getClass())) {
124             // Else Ifs are their own subnode in AST. So we have to
125             // look a little farther up the tree to find the IF statement
126             Node possibleStatement = ((SimpleNode) statement).getFirstParentOfType(ASTIfStatement.class);
127             while(possibleStatement != null && possibleStatement.getClass().equals(ASTIfStatement.class)) {
128                 statement = possibleStatement;
129                 possibleStatement = ((SimpleNode) possibleStatement).getFirstParentOfType(ASTIfStatement.class);
130             }
131         }
132         Map thisBranch = (Map) blocks.get(statement);
133         if (thisBranch == null) {
134             thisBranch = new HashMap();
135             blocks.put(statement, thisBranch);
136         }
137         Integer x = (Integer) thisBranch.get(block);
138         if (x != null) {
139             thisSize += x.intValue();
140         }
141         thisBranch.put(statement, new Integer(thisSize));
142     }
143 
144     private int processBlocks(Map blocks) {
145         int anticipatedLength = 0;
146         int ifLength = 0;
147         for (Iterator iter = blocks.entrySet().iterator(); iter.hasNext();) {
148             Map.Entry entry = (Map.Entry) iter.next();
149             ifLength = 0;
150             for (Iterator iter2 = ((Map) entry.getValue()).entrySet().iterator(); iter2.hasNext();) {
151                 Map.Entry entry2 = (Map.Entry) iter2.next();
152                 Integer value = (Integer) entry2.getValue();
153                 ifLength = Math.max(ifLength, value.intValue());
154             }
155             anticipatedLength += ifLength;
156         }
157         return anticipatedLength;
158     }
159 
160     private int processAdditive(SimpleNode sn) {
161         ASTAdditiveExpression additive = (ASTAdditiveExpression) sn.getFirstChildOfType(ASTAdditiveExpression.class);
162         if (additive == null) {
163             return 0;
164         }
165         int anticipatedLength = 0;
166         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
167             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
168             ASTLiteral literal = (ASTLiteral) childNode.getFirstChildOfType(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 int processNode(SimpleNode sn) {
178         int anticipatedLength = 0;
179         ASTPrimaryPrefix xn = (ASTPrimaryPrefix) sn.getFirstChildOfType(ASTPrimaryPrefix.class);
180         if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0).getClass().equals(ASTLiteral.class)) {
181 /*
182             String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
183             if(regexp.match("/^[\"']/", str)){
184                 anticipatedLength += str.length() - 2;
185             } else {
186                 anticipatedLength += str.length();
187             }*/
188             
189             String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
190             if(regexp.match("/^[\"']/", str)){
191                 anticipatedLength += str.length() - 2;
192             } else if(str.startsWith("0x")){
193                 anticipatedLength += 1;
194             } else {
195                 anticipatedLength += str.length();
196             }            
197         }
198         return anticipatedLength;
199     }
200 
201     private int getConstructorLength(SimpleNode node, int constructorLength) {
202         int iConstructorLength = constructorLength;
203         SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
204         List literal;
205 
206         if (block == null) {
207             block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
208         }
209         if (block == null) {
210             block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
211             if (block != null) {
212                 iConstructorLength = -1;
213             }
214         }
215         literal = (block.findChildrenOfType(ASTLiteral.class));
216         if (literal.size() == 0) {
217             List name = (block.findChildrenOfType(ASTName.class));
218             if (name.size() != 0) {
219                 iConstructorLength = -1;
220             }
221         } else if (literal.size() == 1) {
222             String str = ((SimpleNode) literal.get(0)).getImage();
223             if (str == null) {
224                 iConstructorLength = 0;
225             } else if (regexp.match("/^['\"]/", str)) {
226                 // since it's not taken into account
227                 // anywhere. only count the extra 16
228                 // characters
229                 iConstructorLength = 16; // don't add the constructor's length,
230             } else {
231                 iConstructorLength = Integer.parseInt(str);
232             }
233         } else {
234             iConstructorLength = -1;
235         }
236 
237         return iConstructorLength;
238     }
239 
240     private boolean isAdditive(SimpleNode n) {
241         return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
242     }
243 
244     /***
245      * Locate the block that the given node is in, if any
246      * 
247      * @param node
248      *            The node we're looking for a parent of
249      * @return Node - The node that corresponds to any block that may be a
250      *         parent of this object
251      */
252     private Node getFirstParentBlock(Node node) {
253         Node parentNode = node.jjtGetParent();
254 
255         Node lastNode = node;
256         while (parentNode != null && !blockParents.contains(parentNode.getClass())) {
257             lastNode = parentNode;
258             parentNode = parentNode.jjtGetParent();
259         }
260         if (parentNode != null && ASTIfStatement.class.equals(parentNode.getClass())) {
261             parentNode = lastNode;
262         } else if (parentNode != null && parentNode.getClass().equals(ASTSwitchStatement.class)) {
263             parentNode = getSwitchParent(parentNode, lastNode);
264         }
265         return parentNode;
266     }
267 
268     /***
269      * Determine which SwitchLabel we belong to inside a switch
270      * 
271      * @param parentNode
272      *            The parent node we're looking at
273      * @param lastNode
274      *            The last node processed
275      * @return The parent node for the switch statement
276      */
277     private static Node getSwitchParent(Node parentNode, Node lastNode) {
278         int allChildren = parentNode.jjtGetNumChildren();
279         ASTSwitchLabel label = null;
280         for (int ix = 0; ix < allChildren; ix++) {
281             Node n = parentNode.jjtGetChild(ix);
282             if (n.getClass().equals(ASTSwitchLabel.class)) {
283                 label = (ASTSwitchLabel) n;
284             } else if (n.equals(lastNode)) {
285                 parentNode = label;
286                 break;
287             }
288         }
289         return parentNode;
290     }
291 
292 }