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
125
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
183
184
185
186
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
227
228
229 iConstructorLength = 16;
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 }