001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.strings;
005
006 import java.util.HashSet;
007 import java.util.List;
008 import java.util.Map;
009 import java.util.Set;
010
011 import net.sourceforge.pmd.lang.ast.Node;
012 import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
013 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
014 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
015 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
016 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
017 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
018 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
019 import net.sourceforge.pmd.lang.java.ast.ASTName;
020 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
021 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
022 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
023 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
024 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
025 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
026 import net.sourceforge.pmd.lang.java.ast.TypeNode;
027 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
028 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
029 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
030 import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
031 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
032
033 /**
034 * This rule finds concurrent calls to StringBuffer.append where String literals
035 * are used It would be much better to make these calls using one call to
036 * .append
037 * <p/>
038 * example:
039 * <p/>
040 * <pre>
041 * StringBuffer buf = new StringBuffer();
042 * buf.append("Hello");
043 * buf.append(" ").append("World");
044 * </pre>
045 * <p/>
046 * This would be more eloquently put as:
047 * <p/>
048 * <pre>
049 * StringBuffer buf = new StringBuffer();
050 * buf.append("Hello World");
051 * </pre>
052 * <p/>
053 * The rule takes one parameter, threshold, which defines the lower limit of
054 * consecutive appends before a violation is created. The default is 1.
055 */
056 public class ConsecutiveLiteralAppendsRule extends AbstractJavaRule {
057
058 private final static Set<Class<?>> BLOCK_PARENTS;
059
060 static {
061 BLOCK_PARENTS = new HashSet<Class<?>>();
062 BLOCK_PARENTS.add(ASTForStatement.class);
063 BLOCK_PARENTS.add(ASTWhileStatement.class);
064 BLOCK_PARENTS.add(ASTDoStatement.class);
065 BLOCK_PARENTS.add(ASTIfStatement.class);
066 BLOCK_PARENTS.add(ASTSwitchStatement.class);
067 BLOCK_PARENTS.add(ASTMethodDeclaration.class);
068 }
069
070 private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("threshold", "Max consecutive appends", 1, 10, 1, 1.0f);
071
072 private int threshold = 1;
073
074 public ConsecutiveLiteralAppendsRule() {
075 definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
076 }
077
078 @Override
079 public Object visit(ASTVariableDeclaratorId node, Object data) {
080
081 if (!isStringBuffer(node)) {
082 return data;
083 }
084 threshold = getProperty(THRESHOLD_DESCRIPTOR);
085
086 int concurrentCount = checkConstructor(node, data);
087 Node lastBlock = getFirstParentBlock(node);
088 Node currentBlock = lastBlock;
089 Map<VariableNameDeclaration, List<NameOccurrence>> decls = node.getScope().getVariableDeclarations();
090 Node rootNode = null;
091 // only want the constructor flagged if it's really containing strings
092 if (concurrentCount >= 1) {
093 rootNode = node;
094 }
095 for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : decls.entrySet()) {
096 List<NameOccurrence> decl = entry.getValue();
097 for (NameOccurrence no : decl) {
098 Node n = no.getLocation();
099
100 currentBlock = getFirstParentBlock(n);
101
102 if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
103 if (!no.isPartOfQualifiedName()) {
104 checkForViolation(rootNode, data, concurrentCount);
105 concurrentCount = 0;
106 }
107 continue;
108 }
109 ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
110 int numChildren = s.jjtGetNumChildren();
111 for (int jx = 0; jx < numChildren; jx++) {
112 Node sn = s.jjtGetChild(jx);
113 if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
114 continue;
115 }
116
117 // see if it changed blocks
118 if (currentBlock != null && lastBlock != null && !currentBlock.equals(lastBlock)
119 || currentBlock == null ^ lastBlock == null) {
120 checkForViolation(rootNode, data, concurrentCount);
121 concurrentCount = 0;
122 }
123
124 // if concurrent is 0 then we reset the root to report from
125 // here
126 if (concurrentCount == 0) {
127 rootNode = sn;
128 }
129 if (isAdditive(sn)) {
130 concurrentCount = processAdditive(data, concurrentCount, sn, rootNode);
131 if (concurrentCount != 0) {
132 rootNode = sn;
133 }
134 } else if (!isAppendingStringLiteral(sn)) {
135 checkForViolation(rootNode, data, concurrentCount);
136 concurrentCount = 0;
137 } else {
138 concurrentCount++;
139 }
140 lastBlock = currentBlock;
141 }
142 }
143 }
144 checkForViolation(rootNode, data, concurrentCount);
145 return data;
146 }
147
148 /**
149 * Determine if the constructor contains (or ends with) a String Literal
150 *
151 * @param node
152 * @return 1 if the constructor contains string argument, else 0
153 */
154 private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
155 Node parent = node.jjtGetParent();
156 if (parent.jjtGetNumChildren() >= 2) {
157 ASTArgumentList list = parent.jjtGetChild(1).getFirstDescendantOfType(ASTArgumentList.class);
158 if (list != null) {
159 ASTLiteral literal = list.getFirstDescendantOfType(ASTLiteral.class);
160 if (!isAdditive(list) && literal != null && literal.isStringLiteral()) {
161 return 1;
162 }
163 return processAdditive(data, 0, list, node);
164 }
165 }
166 return 0;
167 }
168
169 private int processAdditive(Object data, int concurrentCount, Node sn, Node rootNode) {
170 ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
171 // The additive expression must of be type String to count
172 if (additive == null || additive.getType() != null && !TypeHelper.isA(additive, String.class)) {
173 return 0;
174 }
175 int count = concurrentCount;
176 boolean found = false;
177 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
178 Node childNode = additive.jjtGetChild(ix);
179 if (childNode.jjtGetNumChildren() != 1 || childNode.hasDescendantOfType(ASTName.class)) {
180 if (!found) {
181 checkForViolation(rootNode, data, count);
182 found = true;
183 }
184 count = 0;
185 } else {
186 count++;
187 }
188 }
189
190 // no variables appended, compiler will take care of merging all the
191 // string concats, we really only have 1 then
192 if (!found) {
193 count = 1;
194 }
195
196 return count;
197 }
198
199 /**
200 * Checks to see if there is string concatenation in the node.
201 *
202 * This method checks if it's additive with respect to the append method
203 * only.
204 *
205 * @param n
206 * Node to check
207 * @return true if the node has an additive expression (i.e. "Hello " +
208 * Const.WORLD)
209 */
210 private boolean isAdditive(Node n) {
211 List<ASTAdditiveExpression> lstAdditive = n.findDescendantsOfType(ASTAdditiveExpression.class);
212 if (lstAdditive.isEmpty()) {
213 return false;
214 }
215 // if there are more than 1 set of arguments above us we're not in the
216 // append
217 // but a sub-method call
218 for (int ix = 0; ix < lstAdditive.size(); ix++) {
219 ASTAdditiveExpression expr = lstAdditive.get(ix);
220 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
221 return false;
222 }
223 }
224 return true;
225 }
226
227 /**
228 * Get the first parent. Keep track of the last node though. For If
229 * statements it's the only way we can differentiate between if's and else's
230 * For switches it's the only way we can differentiate between switches
231 *
232 * @param node The node to check
233 * @return The first parent block
234 */
235 private Node getFirstParentBlock(Node node) {
236 Node parentNode = node.jjtGetParent();
237
238 Node lastNode = node;
239 while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
240 lastNode = parentNode;
241 parentNode = parentNode.jjtGetParent();
242 }
243 if (parentNode instanceof ASTIfStatement) {
244 parentNode = lastNode;
245 } else if (parentNode instanceof ASTSwitchStatement) {
246 parentNode = getSwitchParent(parentNode, lastNode);
247 }
248 return parentNode;
249 }
250
251 /**
252 * Determine which SwitchLabel we belong to inside a switch
253 *
254 * @param parentNode The parent node we're looking at
255 * @param lastNode The last node processed
256 * @return The parent node for the switch statement
257 */
258 private Node getSwitchParent(Node parentNode, Node lastNode) {
259 int allChildren = parentNode.jjtGetNumChildren();
260 ASTSwitchLabel label = null;
261 for (int ix = 0; ix < allChildren; ix++) {
262 Node n = parentNode.jjtGetChild(ix);
263 if (n instanceof ASTSwitchLabel) {
264 label = (ASTSwitchLabel) n;
265 } else if (n.equals(lastNode)) {
266 parentNode = label;
267 break;
268 }
269 }
270 return parentNode;
271 }
272
273 /**
274 * Helper method checks to see if a violation occured, and adds a
275 * RuleViolation if it did
276 */
277 private void checkForViolation(Node node, Object data, int concurrentCount) {
278 if (concurrentCount > threshold) {
279 String[] param = { String.valueOf(concurrentCount) };
280 addViolation(data, node, param);
281 }
282 }
283
284 private boolean isAppendingStringLiteral(Node node) {
285 Node n = node;
286 while (n.jjtGetNumChildren() != 0 && !(n instanceof ASTLiteral)) {
287 n = n.jjtGetChild(0);
288 }
289 return n instanceof ASTLiteral;
290 }
291
292 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
293
294 if (node.getType() != null) {
295 return node.getType().equals(StringBuffer.class);
296 }
297 Node nn = node.getTypeNameNode();
298 if (nn.jjtGetNumChildren() == 0) {
299 return false;
300 }
301 return TypeHelper.isA((TypeNode) nn.jjtGetChild(0), StringBuffer.class);
302 }
303 }
|