Clover coverage report - PMD - 3.9 Coverage timestamp: Tue Dec 19 2006 09:38:44 EST FRAMES NO FRAMES |
file stats: | LOC: | 889 |  | Methods: | 47
| NCLOC: | 553 |  | Classes: | 7
Source file | Conditionals | Statements | Methods | TOTAL
ConstructorCallsOverridableMethod.java | 50% | 68.3% | 95.7% | 65.3% |

1 |  | /**
2 |  | * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 |  | */
4 |  | package net.sourceforge.pmd.rules;
5 |  |
6 |  | import net.sourceforge.pmd.AbstractRule;
7 |  | import net.sourceforge.pmd.ast.ASTArguments;
8 |  | import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
9 |  | import net.sourceforge.pmd.ast.ASTCompilationUnit;
10 |  | import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
11 |  | import net.sourceforge.pmd.ast.ASTEnumDeclaration;
12 |  | import net.sourceforge.pmd.ast.ASTExplicitConstructorInvocation;
13 |  | import net.sourceforge.pmd.ast.ASTLiteral;
14 |  | import net.sourceforge.pmd.ast.ASTMethodDeclaration;
15 |  | import net.sourceforge.pmd.ast.ASTMethodDeclarator;
16 |  | import net.sourceforge.pmd.ast.ASTName;
17 |  | import net.sourceforge.pmd.ast.ASTPrimaryExpression;
18 |  | import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
19 |  | import net.sourceforge.pmd.ast.ASTPrimarySuffix;
20 |  | import net.sourceforge.pmd.ast.AccessNode;
21 |  | import net.sourceforge.pmd.ast.Node;
22 |  | import net.sourceforge.pmd.ast.SimpleNode;
23 |  |
24 |  | import java.util.ArrayList;
25 |  | import java.util.Collections;
26 |  | import java.util.HashMap;
27 |  | import java.util.Iterator;
28 |  | import java.util.List;
29 |  | import java.util.Map;
30 |  | import java.util.Set;
31 |  |
32 |  | /**
33 |  | * Searches through all methods and constructors called from constructors. It
34 |  | * marks as dangerous any call to overridable methods from non-private
35 |  | * constructors. It marks as dangerous any calls to dangerous private constructors
36 |  | * from non-private constructors.
37 |  | *
38 |  | * @author CL Gilbert (dnoyeb@users.sourceforge.net)
39 |  | * @todo match parameter types. Aggressively strips off any package names. Normal
40 |  | * compares the names as is.
41 |  | * @todo What about interface declarations which can have internal classes
42 |  | */
43 |  | public final class ConstructorCallsOverridableMethod extends AbstractRule {
44 |  | /**
45 |  | * 2: method();
46 |  | * ASTPrimaryPrefix
47 |  | * ASTName image = "method"
48 |  | * ASTPrimarySuffix
49 |  | * *ASTArguments
50 |  | * 3: a.method();
51 |  | * ASTPrimaryPrefix ->
52 |  | * ASTName image = "a.method" ???
53 |  | * ASTPrimarySuffix -> ()
54 |  | * ASTArguments
55 |  | * 3: this.method();
56 |  | * ASTPrimaryPrefix -> this image=null
57 |  | * ASTPrimarySuffix -> method
58 |  | * ASTPrimarySuffix -> ()
59 |  | * ASTArguments
60 |  | * <p/>
61 |  | * super.method();
62 |  | * ASTPrimaryPrefix -> image = "method"
63 |  | * ASTPrimarySuffix -> image = null
64 |  | * ASTArguments ->
65 |  | * <p/>
66 |  | * super.a.method();
67 |  | * ASTPrimaryPrefix -> image = "a"
68 |  | * ASTPrimarySuffix -> image = "method"
69 |  | * ASTPrimarySuffix -> image = null
70 |  | * ASTArguments ->
71 |  | * <p/>
72 |  | * <p/>
73 |  | * 4: this.a.method();
74 |  | * ASTPrimaryPrefix -> image = null
75 |  | * ASTPrimarySuffix -> image = "a"
76 |  | * ASTPrimarySuffix -> image = "method"
77 |  | * ASTPrimarySuffix ->
78 |  | * ASTArguments
79 |  | * <p/>
80 |  | * 4: ClassName.this.method();
81 |  | * ASTPrimaryPrefix
82 |  | * ASTName image = "ClassName"
83 |  | * ASTPrimarySuffix -> this image=null
84 |  | * ASTPrimarySuffix -> image = "method"
85 |  | * ASTPrimarySuffix -> ()
86 |  | * ASTArguments
87 |  | * 5: ClassName.this.a.method();
88 |  | * ASTPrimaryPrefix
89 |  | * ASTName image = "ClassName"
90 |  | * ASTPrimarySuffix -> this image=null
91 |  | * ASTPrimarySuffix -> image="a"
92 |  | * ASTPrimarySuffix -> image="method"
93 |  | * ASTPrimarySuffix -> ()
94 |  | * ASTArguments
95 |  | * 5: Package.ClassName.this.method();
96 |  | * ASTPrimaryPrefix
97 |  | * ASTName image ="Package.ClassName"
98 |  | * ASTPrimarySuffix -> this image=null
99 |  | * ASTPrimarySuffix -> image="method"
100 |  | * ASTPrimarySuffix -> ()
101 |  | * ASTArguments
102 |  | * 6: Package.ClassName.this.a.method();
103 |  | * ASTPrimaryPrefix
104 |  | * ASTName image ="Package.ClassName"
105 |  | * ASTPrimarySuffix -> this image=null
106 |  | * ASTPrimarySuffix -> a
107 |  | * ASTPrimarySuffix -> method
108 |  | * ASTPrimarySuffix -> ()
109 |  | * ASTArguments
110 |  | * 5: OuterClass.InnerClass.this.method();
111 |  | * ASTPrimaryPrefix
112 |  | * ASTName image = "OuterClass.InnerClass"
113 |  | * ASTPrimarySuffix -> this image=null
114 |  | * ASTPrimarySuffix -> method
115 |  | * ASTPrimarySuffix -> ()
116 |  | * ASTArguments
117 |  | * 6: OuterClass.InnerClass.this.a.method();
118 |  | * ASTPrimaryPrefix
119 |  | * ASTName image = "OuterClass.InnerClass"
120 |  | * ASTPrimarySuffix -> this image=null
121 |  | * ASTPrimarySuffix -> a
122 |  | * ASTPrimarySuffix -> method
123 |  | * ASTPrimarySuffix -> ()
124 |  | * ASTArguments
125 |  | * <p/>
126 |  | * OuterClass.InnerClass.this.a.method().method().method();
127 |  | * ASTPrimaryPrefix
128 |  | * ASTName image = "OuterClass.InnerClass"
129 |  | * ASTPrimarySuffix -> this image=null
130 |  | * ASTPrimarySuffix -> a image='a'
131 |  | * ASTPrimarySuffix -> method image='method'
132 |  | * ASTPrimarySuffix -> () image=null
133 |  | * ASTArguments
134 |  | * ASTPrimarySuffix -> method image='method'
135 |  | * ASTPrimarySuffix -> () image=null
136 |  | * ASTArguments
137 |  | * ASTPrimarySuffix -> method image='method'
138 |  | * ASTPrimarySuffix -> () image=null
139 |  | * ASTArguments
140 |  | * <p/>
141 |  | * 3..n: Class.InnerClass[0].InnerClass[n].this.method();
142 |  | * ASTPrimaryPrefix
143 |  | * ASTName image = "Class[0]..InnerClass[n]"
144 |  | * ASTPrimarySuffix -> image=null
145 |  | * ASTPrimarySuffix -> method
146 |  | * ASTPrimarySuffix -> ()
147 |  | * ASTArguments
148 |  | * <p/>
149 |  | * super.aMethod();
150 |  | * ASTPrimaryPrefix -> aMethod
151 |  | * ASTPrimarySuffix -> ()
152 |  | * <p/>
153 |  | * Evaluate right to left
154 |  | */
155 |  | private static class MethodInvocation {
156 |  | private String m_Name;
157 |  | private ASTPrimaryExpression m_Ape;
158 |  | private List m_ReferenceNames;
159 |  | private List m_QualifierNames;
160 |  | private int m_ArgumentSize;
161 |  | private boolean m_Super;
162 |  |
163 | 9 | private MethodInvocation(ASTPrimaryExpression ape, List qualifierNames, List referenceNames, String name, int argumentSize, boolean superCall) {
164 | 9 | m_Ape = ape;
165 | 9 | m_QualifierNames = qualifierNames;
166 | 9 | m_ReferenceNames = referenceNames;
167 | 9 | m_Name = name;
168 | 9 | m_ArgumentSize = argumentSize;
169 | 9 | m_Super = superCall;
170 |  | }
171 |  |
172 | 9 | public boolean isSuper() {
173 | 9 | return m_Super;
174 |  | }
175 |  |
176 | 6 | public String getName() {
177 | 6 | return m_Name;
178 |  | }
179 |  |
180 | 4 | public int getArgumentCount() {
181 | 4 | return m_ArgumentSize;
182 |  | }
183 |  |
184 | 9 | public List getReferenceNames() {
185 | 9 | return m_ReferenceNames;//new ArrayList(variableNames);
186 |  | }
187 |  |
188 | 9 | public List getQualifierNames() {
189 | 9 | return m_QualifierNames;
190 |  | }
191 |  |
192 | 3 | public ASTPrimaryExpression getASTPrimaryExpression() {
193 | 3 | return m_Ape;
194 |  | }
195 |  |
196 | 11 | public static MethodInvocation getMethod(ASTPrimaryExpression node) {
197 | 11 | MethodInvocation meth = null;
198 | 11 | int i = node.jjtGetNumChildren();
199 | 11 | if (i > 1) {//should always be at least 2, probably can eliminate this check
200 |  | //start at end which is guaranteed, work backwards
201 | 9 | Node lastNode = node.jjtGetChild(i - 1);
202 | 9 | if ((lastNode.jjtGetNumChildren() == 1) && (lastNode.jjtGetChild(0) instanceof ASTArguments)) { //could be ASTExpression for instance 'a[4] = 5';
203 |  | //start putting method together
204 |  | // System.out.println("Putting method together now");
205 | 9 | List varNames = new ArrayList();
206 | 9 | List packagesAndClasses = new ArrayList(); //look in JLS for better name here;
207 | 9 | String methodName = null;
208 | 9 | ASTArguments args = (ASTArguments) lastNode.jjtGetChild(0);
209 | 9 | int numOfArguments = args.getArgumentCount();
210 | 9 | boolean superFirst = false;
211 | 9 | int thisIndex = -1;
212 |  |
213 | 9 | FIND_SUPER_OR_THIS: {
214 |  | //search all nodes except last for 'this' or 'super'. will be at: (node 0 | node 1 | nowhere)
215 |  | //this is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments)
216 |  | //this is an ASTPrimaryPrefix with a null image and an ASTName that has a null image
217 |  | //super is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments)
218 |  | //super is an ASTPrimaryPrefix with a non-null image
219 | 9 | for (int x = 0; x < i - 1; x++) {
220 | 9 | Node child = node.jjtGetChild(x);
221 | 9 | if (child instanceof ASTPrimarySuffix) { //check suffix type match
222 | 0 | ASTPrimarySuffix child2 = (ASTPrimarySuffix) child;
223 |  | // String name = getNameFromSuffix((ASTPrimarySuffix)child);
224 |  | // System.out.println("found name suffix of : " + name);
225 | 0 | if (child2.getImage() == null && child2.jjtGetNumChildren() == 0) {
226 | 0 | thisIndex = x;
227 | 0 | break;
228 |  | }
229 |  | //could be super, could be this. currently we cant tell difference so we miss super when
230 |  | //XYZ.ClassName.super.method();
231 |  | //still works though.
232 | 9 | } else if (child instanceof ASTPrimaryPrefix) { //check prefix type match
233 | 9 | ASTPrimaryPrefix child2 = (ASTPrimaryPrefix) child;
234 | 9 | if (getNameFromPrefix(child2) == null) {
235 | 0 | if (child2.getImage() == null) {
236 | 0 | thisIndex = x;
237 | 0 | break;
238 |  | } else {//happens when super is used [super.method(): image = 'method']
239 | 0 | superFirst = true;
240 | 0 | thisIndex = x;
241 |  | //the true super is at an unusable index because super.method() has only 2 nodes [method=0,()=1]
242 |  | //as opposed to the 3 you might expect and which this.method() actually has. [this=0,method=1.()=2]
243 | 0 | break;
244 |  | }
245 |  | }
246 |  | }
247 |  | // else{
248 |  | // System.err.println("Bad Format error"); //throw exception, quit evaluating this compilation node
249 |  | // }
250 |  | }
251 |  | }
252 |  |
253 | 9 | if (thisIndex != -1) {
254 |  | // System.out.println("Found this or super: " + thisIndex);
255 |  | //Hack that must be removed if and when the patters of super.method() begins to logically match the rest of the patterns !!!
256 | 0 | if (superFirst) { //this is when super is the first node of statement. no qualifiers, all variables or method
257 |  | // System.out.println("super first");
258 | 0 | FIRSTNODE:{
259 | 0 | ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
260 | 0 | String name = child.getImage();//special case
261 | 0 | if (i == 2) { //last named node = method name
262 | 0 | methodName = name;
263 |  | } else { //not the last named node so its only var name
264 | 0 | varNames.add(name);
265 |  | }
266 |  | }
267 | 0 | OTHERNODES:{ //variables
268 | 0 | for (int x = 1; x < i - 1; x++) {
269 | 0 | Node child = node.jjtGetChild(x);
270 | 0 | ASTPrimarySuffix ps = (ASTPrimarySuffix) child;
271 | 0 | if (!ps.isArguments()) {
272 | 0 | String name = ((ASTPrimarySuffix) child).getImage();
273 | 0 | if (x == i - 2) {//last node
274 | 0 | methodName = name;
275 |  | } else {//not the last named node so its only var name
276 | 0 | varNames.add(name);
277 |  | }
278 |  | }
279 |  | }
280 |  | }
281 |  | } else {//not super call
282 | 0 | FIRSTNODE:{
283 | 0 | if (thisIndex == 1) {//qualifiers in node 0
284 | 0 | ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
285 | 0 | String toParse = getNameFromPrefix(child);
286 |  | // System.out.println("parsing for class/package names in : " + toParse);
287 | 0 | java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
288 | 0 | while (st.hasMoreTokens()) {
289 | 0 | packagesAndClasses.add(st.nextToken());
290 |  | }
291 |  | }
292 |  | }
293 | 0 | OTHERNODES:{ //other methods called in this statement are grabbed here
294 |  | //this is at 0, then no Qualifiers
295 |  | //this is at 1, the node 0 contains qualifiers
296 | 0 | for (int x = thisIndex + 1; x < i - 1; x++) {//everything after this is var name or method name
297 | 0 | ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
298 | 0 | if (!child.isArguments()) { //skip the () of method calls
299 | 0 | String name = child.getImage();
300 |  | // System.out.println("Found suffix: " + suffixName);
301 | 0 | if (x == i - 2) {
302 | 0 | methodName = name;
303 |  | } else {
304 | 0 | varNames.add(name);
305 |  | }
306 |  | }
307 |  | }
308 |  | }
309 |  | }
310 |  | } else { //if no this or super found, everything is method name or variable
311 |  | //System.out.println("no this found:");
312 | 9 | FIRSTNODE:{ //variable names are in the prefix + the first method call [a.b.c.x()]
313 | 9 | ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0);
314 | 9 | String toParse = getNameFromPrefix(child);
315 |  | // System.out.println("parsing for var names in : " + toParse);
316 | 9 | java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, ".");
317 | 9 | while (st.hasMoreTokens()) {
318 | 9 | String value = st.nextToken();
319 | 9 | if (!st.hasMoreTokens()) {
320 | 9 | if (i == 2) {//if this expression is 2 nodes long, then the last part of prefix is method name
321 | 9 | methodName = value;
322 |  | } else {
323 | 0 | varNames.add(value);
324 |  | }
325 |  | } else { //variable name
326 | 0 | varNames.add(value);
327 |  | }
328 |  | }
329 |  | }
330 | 9 | OTHERNODES:{ //other methods called in this statement are grabbed here
331 | 9 | for (int x = 1; x < i - 1; x++) {
332 | 0 | ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x);
333 | 0 | if (!child.isArguments()) {
334 | 0 | String name = child.getImage();
335 | 0 | if (x == i - 2) {
336 | 0 | methodName = name;
337 |  | } else {
338 | 0 | varNames.add(name);
339 |  | }
340 |  | }
341 |  | }
342 |  | }
343 |  | }
344 | 9 | meth = new MethodInvocation(node, packagesAndClasses, varNames, methodName, numOfArguments, superFirst);
345 |  | }
346 |  | }
347 | 11 | return meth;
348 |  | }
349 |  |
350 | 0 | public void show() {
351 | 0 | System.out.println("<MethodInvocation>");
352 | 0 | List pkg = getQualifierNames();
353 | 0 | System.out.println(" <Qualifiers>");
354 | 0 | for (Iterator it = pkg.iterator(); it.hasNext();) {
355 | 0 | String name = (String) it.next();
356 | 0 | System.out.println(" " + name);
357 |  | }
358 | 0 | System.out.println(" </Qualifiers>");
359 | 0 | System.out.println(" <Super>" + isSuper() + "</Super>");
360 | 0 | List vars = getReferenceNames();
361 | 0 | System.out.println(" <References>");
362 | 0 | for (Iterator it = vars.iterator(); it.hasNext();) {
363 | 0 | String name = (String) it.next();
364 | 0 | System.out.println(" " + name);
365 |  | }
366 | 0 | System.out.println(" </References>");
367 | 0 | System.out.println(" <Name>" + getName() + "</Name>");
368 | 0 | System.out.println("</MethodInvocation>");
369 |  | }
370 |  | }
371 |  |
372 |  | private static final class ConstructorInvocation {
373 |  | private ASTExplicitConstructorInvocation m_Eci;
374 |  | private String name;
375 |  | private int count = 0;
376 |  |
377 | 1 | public ConstructorInvocation(ASTExplicitConstructorInvocation eci) {
378 | 1 | m_Eci = eci;
379 | 1 | List l = new ArrayList();
380 | 1 | eci.findChildrenOfType(ASTArguments.class, l);
381 | 1 | if (!l.isEmpty()) {
382 | 1 | ASTArguments aa = (ASTArguments) l.get(0);
383 | 1 | count = aa.getArgumentCount();
384 |  | }
385 | 1 | name = eci.getImage();
386 |  | }
387 |  |
388 | 2 | public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
389 | 2 | return m_Eci;
390 |  | }
391 |  |
392 | 1 | public int getArgumentCount() {
393 | 1 | return count;
394 |  | }
395 |  |
396 | 0 | public String getName() {
397 | 0 | return name;
398 |  | }
399 |  | }
400 |  |
401 |  | private static final class MethodHolder {
402 |  | private ASTMethodDeclarator amd;
403 |  | private boolean dangerous;
404 |  | private String called;
405 |  |
406 | 10 | public MethodHolder(ASTMethodDeclarator amd) {
407 | 10 | this.amd = amd;
408 |  | }
409 |  |
410 | 7 | public void setCalledMethod(String name) {
411 | 7 | this.called = name;
412 |  | }
413 |  |
414 | 3 | public String getCalled() {
415 | 3 | return this.called;
416 |  | }
417 |  |
418 | 12 | public ASTMethodDeclarator getASTMethodDeclarator() {
419 | 12 | return amd;
420 |  | }
421 |  |
422 | 10 | public boolean isDangerous() {
423 | 10 | return dangerous;
424 |  | }
425 |  |
426 | 7 | public void setDangerous() {
427 | 7 | dangerous = true;
428 |  | }
429 |  | }
430 |  |
431 |  | private final class ConstructorHolder {
432 |  | private ASTConstructorDeclaration m_Cd;
433 |  | private boolean m_Dangerous;
434 |  | private ConstructorInvocation m_Ci;
435 |  | private boolean m_CiInitialized;
436 |  |
437 | 10 | public ConstructorHolder(ASTConstructorDeclaration cd) {
438 | 10 | m_Cd = cd;
439 |  | }
440 |  |
441 | 1 | public ASTConstructorDeclaration getASTConstructorDeclaration() {
442 | 1 | return m_Cd;
443 |  | }
444 |  |
445 | 2 | public ConstructorInvocation getCalledConstructor() {
446 | 2 | if (!m_CiInitialized) {
447 | 1 | initCI();
448 |  | }
449 | 2 | return m_Ci;
450 |  | }
451 |  |
452 | 9 | public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() {
453 | 9 | ASTExplicitConstructorInvocation eci = null;
454 | 9 | if (!m_CiInitialized) {
455 | 9 | initCI();
456 |  | }
457 | 9 | if (m_Ci != null) {
458 | 1 | eci = m_Ci.getASTExplicitConstructorInvocation();
459 |  | }
460 | 9 | return eci;
461 |  | }
462 |  |
463 | 10 | private void initCI() {
464 | 10 | List expressions = new ArrayList();
465 | 10 | m_Cd.findChildrenOfType(ASTExplicitConstructorInvocation.class, expressions); //only 1...
466 | 10 | if (!expressions.isEmpty()) {
467 | 1 | ASTExplicitConstructorInvocation eci = (ASTExplicitConstructorInvocation) expressions.get(0);
468 | 1 | m_Ci = new ConstructorInvocation(eci);
469 |  | //System.out.println("Const call " + eci.getImage()); //super or this???
470 |  | }
471 | 10 | m_CiInitialized = true;
472 |  | }
473 |  |
474 | 3 | public boolean isDangerous() {
475 | 3 | return m_Dangerous;
476 |  | }
477 |  |
478 | 1 | public void setDangerous(boolean dangerous) {
479 | 1 | m_Dangerous = dangerous;
480 |  | }
481 |  | }
482 |  |
483 |  | /**
484 |  | * 1 package per class. holds info for evaluating a single class.
485 |  | */
486 |  | private static class EvalPackage {
487 | 43 | public EvalPackage() {
488 |  | }
489 |  |
490 | 9 | public EvalPackage(String className) {
491 | 9 | m_ClassName = className;
492 | 9 | calledMethods = new ArrayList();//meths called from constructor
493 | 9 | allMethodsOfClass = new HashMap();
494 | 9 | calledConstructors = new ArrayList();//all constructors called from constructor
495 | 9 | allPrivateConstructorsOfClass = new HashMap();
496 |  | }
497 |  |
498 |  | public String m_ClassName;
499 |  | public List calledMethods;
500 |  | public Map allMethodsOfClass;
501 |  |
502 |  | public List calledConstructors;
503 |  | public Map allPrivateConstructorsOfClass;
504 |  | }
505 |  |
506 |  | private static final class NullEvalPackage extends EvalPackage {
507 | 43 | public NullEvalPackage() {
508 | 43 | m_ClassName = "";
509 | 43 | calledMethods = Collections.EMPTY_LIST;
510 | 43 | allMethodsOfClass = Collections.EMPTY_MAP;
511 | 43 | calledConstructors = Collections.EMPTY_LIST;
512 | 43 | allPrivateConstructorsOfClass = Collections.EMPTY_MAP;
513 |  | }
514 |  | }
515 |  |
516 |  | private static final NullEvalPackage nullEvalPackage = new NullEvalPackage();
517 |  |
518 |  |
519 |  | /**
520 |  | * 1 package per class.
521 |  | */
522 |  | private final List evalPackages = new ArrayList();//could use java.util.Stack
523 |  |
524 | 132 | private EvalPackage getCurrentEvalPackage() {
525 | 132 | return (EvalPackage) evalPackages.get(evalPackages.size() - 1);
526 |  | }
527 |  |
528 |  | /**
529 |  | * Adds and evaluation package and makes it current
530 |  | */
531 | 9 | private void putEvalPackage(EvalPackage ep) {
532 | 9 | evalPackages.add(ep);
533 |  | }
534 |  |
535 | 9 | private void removeCurrentEvalPackage() {
536 | 9 | evalPackages.remove(evalPackages.size() - 1);
537 |  | }
538 |  |
539 | 10 | private void clearEvalPackages() {
540 | 10 | evalPackages.clear();
541 |  | }
542 |  |
543 |  | /**
544 |  | * This check must be evaluated independelty for each class. Inner classses
545 |  | * get their own EvalPackage in order to perform independent evaluation.
546 |  | */
547 | 9 | private Object visitClassDec(ASTClassOrInterfaceDeclaration node, Object data) {
548 | 9 | String className = node.getImage();
549 | 9 | if (!node.isFinal() && !node.isStatic()) {
550 | 9 | putEvalPackage(new EvalPackage(className));
551 |  | } else {
552 | 0 | putEvalPackage(nullEvalPackage);
553 |  | }
554 |  | //store any errors caught from other passes.
555 | 9 | super.visit((ASTClassOrInterfaceDeclaration) node, data);
556 |  |
557 |  | //skip this class if it has no evaluation package
558 | 9 | if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {
559 |  | //evaluate danger of all methods in class
560 | 9 | while (evaluateDangerOfMethods(getCurrentEvalPackage().allMethodsOfClass)) {
561 |  | }
562 |  | //evaluate danger of constructors
563 | 9 | evaluateDangerOfConstructors1(getCurrentEvalPackage().allPrivateConstructorsOfClass, getCurrentEvalPackage().allMethodsOfClass.keySet());
564 | 9 | while (evaluateDangerOfConstructors2(getCurrentEvalPackage().allPrivateConstructorsOfClass)) {
565 |  | }
566 |  |
567 |  | //get each method called on this object from a non-private constructor, if its dangerous flag it
568 | 9 | for (Iterator it = getCurrentEvalPackage().calledMethods.iterator(); it.hasNext();) {
569 | 7 | MethodInvocation meth = (MethodInvocation) it.next();
570 |  | //check against each dangerous method in class
571 | 7 | for (Iterator it2 = getCurrentEvalPackage().allMethodsOfClass.keySet().iterator(); it2.hasNext();) {
572 | 8 | MethodHolder h = (MethodHolder) it2.next();
573 | 8 | if (h.isDangerous()) {
574 | 5 | String methName = h.getASTMethodDeclarator().getImage();
575 | 5 | int count = h.getASTMethodDeclarator().getParameterCount();
576 | 5 | if (methName.equals(meth.getName()) && meth.getArgumentCount() == count) {
577 | 3 | addViolation(data, meth.getASTPrimaryExpression(), "method '" + h.getCalled() + "'");
578 |  | }
579 |  | }
580 |  | }
581 |  | }
582 |  | //get each unsafe private constructor, and check if its called from any non private constructors
583 | 9 | for (Iterator privConstIter = getCurrentEvalPackage().allPrivateConstructorsOfClass.keySet().iterator(); privConstIter.hasNext();) {
584 | 1 | ConstructorHolder ch = (ConstructorHolder) privConstIter.next();
585 | 1 | if (ch.isDangerous()) { //if its dangerous check if its called from any non-private constructors
586 |  | //System.out.println("visitClassDec Evaluating dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
587 | 1 | int paramCount = ch.getASTConstructorDeclaration().getParameterCount();
588 | 1 | for (Iterator calledConstIter = getCurrentEvalPackage().calledConstructors.iterator(); calledConstIter.hasNext();) {
589 | 1 | ConstructorInvocation ci = (ConstructorInvocation) calledConstIter.next();
590 | 1 | if (ci.getArgumentCount() == paramCount) {
591 |  | //match name super / this !?
592 | 1 | addViolation(data, ci.getASTExplicitConstructorInvocation(), "constructor");
593 |  | }
594 |  | }
595 |  | }
596 |  | }
597 |  | }
598 |  | //finished evaluating this class, move up a level
599 | 9 | removeCurrentEvalPackage();
600 | 9 | return data;
601 |  | }
602 |  |
603 |  | /**
604 |  | * Check the methods called on this class by each of the methods on this
605 |  | * class. If a method calls an unsafe method, mark the calling method as
606 |  | * unsafe. This changes the list of unsafe methods which necessitates
607 |  | * another pass. Keep passing until you make a clean pass in which no
608 |  | * methods are changed to unsafe.
609 |  | * For speed it is possible to limit the number of passes.
610 |  | * <p/>
611 |  | * Impossible to tell type of arguments to method, so forget method matching
612 |  | * on types. just use name and num of arguments. will be some false hits,
613 |  | * but oh well.
614 |  | *
615 |  | * @todo investigate limiting the number of passes through config.
616 |  | */
617 | 9 | private boolean evaluateDangerOfMethods(Map classMethodMap) {
618 |  | //check each method if it calls overridable method
619 | 9 | boolean found = false;
620 | 9 | for (Iterator methodsIter = classMethodMap.entrySet().iterator(); methodsIter.hasNext();) {
621 | 10 | Map.Entry entry = (Map.Entry) methodsIter.next();
622 |  |
623 | 10 | MethodHolder h = (MethodHolder) entry.getKey();
624 | 10 | List calledMeths = (List) entry.getValue();
625 | 10 | for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !h.isDangerous();) {
626 |  | //if this method matches one of our dangerous methods, mark it dangerous
627 | 0 | MethodInvocation meth = (MethodInvocation) calledMethsIter.next();
628 |  | //System.out.println("Called meth is " + meth);
629 | 0 | for (Iterator innerMethsIter = classMethodMap.keySet().iterator(); innerMethsIter.hasNext();) { //need to skip self here h == h3
630 | 0 | MethodHolder h3 = (MethodHolder) innerMethsIter.next();
631 | 0 | if (h3.isDangerous()) {
632 | 0 | String matchMethodName = h3.getASTMethodDeclarator().getImage();
633 | 0 | int matchMethodParamCount = h3.getASTMethodDeclarator().getParameterCount();
634 |  | //System.out.println("matching " + matchMethodName + " to " + meth.getName());
635 | 0 | if (matchMethodName.equals(meth.getName()) && matchMethodParamCount == meth.getArgumentCount()) {
636 | 0 | h.setDangerous();
637 | 0 | h.setCalledMethod(matchMethodName);
638 | 0 | found = true;
639 | 0 | break;
640 |  | }
641 |  | }
642 |  | }
643 |  | }
644 |  | }
645 | 9 | return found;
646 |  | }
647 |  |
648 |  | /**
649 |  | * marks constructors dangerous if they call any dangerous methods
650 |  | * Requires only a single pass as methods are already marked
651 |  | *
652 |  | * @todo optimize by having methods already evaluated somehow!?
653 |  | */
654 | 9 | private void evaluateDangerOfConstructors1(Map classConstructorMap, Set evaluatedMethods) {
655 |  | //check each constructor in the class
656 | 9 | for (Iterator constIter = classConstructorMap.entrySet().iterator(); constIter.hasNext();) {
657 | 1 | Map.Entry entry = (Map.Entry) constIter.next();
658 | 1 | ConstructorHolder ch = (ConstructorHolder) entry.getKey();
659 | 1 | if (!ch.isDangerous()) {//if its not dangerous then evaluate if it should be
660 |  | //if it calls dangerous method mark it as dangerous
661 | 1 | List calledMeths = (List) entry.getValue();
662 |  | //check each method it calls
663 | 1 | for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !ch.isDangerous();) {//but thee are diff objects which represent same thing but were never evaluated, they need reevaluation
664 | 1 | MethodInvocation meth = (MethodInvocation) calledMethsIter.next();//CCE
665 | 1 | String methName = meth.getName();
666 | 1 | int methArgCount = meth.getArgumentCount();
667 |  | //check each of the already evaluated methods: need to optimize this out
668 | 1 | for (Iterator evaldMethsIter = evaluatedMethods.iterator(); evaldMethsIter.hasNext();) {
669 | 1 | MethodHolder h = (MethodHolder) evaldMethsIter.next();
670 | 1 | if (h.isDangerous()) {
671 | 1 | String matchName = h.getASTMethodDeclarator().getImage();
672 | 1 | int matchParamCount = h.getASTMethodDeclarator().getParameterCount();
673 | 1 | if (methName.equals(matchName) && (methArgCount == matchParamCount)) {
674 | 1 | ch.setDangerous(true);
675 |  | //System.out.println("evaluateDangerOfConstructors1 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
676 | 1 | break;
677 |  | }
678 |  | }
679 |  |
680 |  | }
681 |  | }
682 |  | }
683 |  | }
684 |  | }
685 |  |
686 |  | /**
687 |  | * Constructor map should contain a key for each private constructor, and
688 |  | * maps to a List which contains all called constructors of that key.
689 |  | * marks dangerous if call dangerous private constructor
690 |  | * we ignore all non-private constructors here. That is, the map passed in
691 |  | * should not contain any non-private constructors.
692 |  | * we return boolean in order to limit the number of passes through this method
693 |  | * but it seems as if we can forgo that and just process it till its done.
694 |  | */
695 | 9 | private boolean evaluateDangerOfConstructors2(Map classConstructorMap) {
696 | 9 | boolean found = false;//triggers on danger state change
697 |  | //check each constructor in the class
698 | 9 | for (Iterator constIter = classConstructorMap.keySet().iterator(); constIter.hasNext();) {
699 | 1 | ConstructorHolder ch = (ConstructorHolder) constIter.next();
700 | 1 | ConstructorInvocation calledC = ch.getCalledConstructor();
701 | 1 | if (calledC == null || ch.isDangerous()) {
702 | 1 | continue;
703 |  | }
704 |  | //if its not dangerous then evaluate if it should be
705 |  | //if it calls dangerous constructor mark it as dangerous
706 | 0 | int cCount = calledC.getArgumentCount();
707 | 0 | for (Iterator innerConstIter = classConstructorMap.keySet().iterator(); innerConstIter.hasNext() && !ch.isDangerous();) { //forget skipping self because that introduces another check for each, but only 1 hit
708 | 0 | ConstructorHolder h2 = (ConstructorHolder) innerConstIter.next();
709 | 0 | if (h2.isDangerous()) {
710 | 0 | int matchConstArgCount = h2.getASTConstructorDeclaration().getParameterCount();
711 | 0 | if (matchConstArgCount == cCount) {
712 | 0 | ch.setDangerous(true);
713 | 0 | found = true;
714 |  | //System.out.println("evaluateDangerOfConstructors2 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params");
715 |  | }
716 |  | }
717 |  | }
718 |  | }
719 | 9 | return found;
720 |  | }
721 |  |
722 | 10 | public Object visit(ASTCompilationUnit node, Object data) {
723 | 10 | clearEvalPackages();
724 | 10 | return super.visit(node, data);
725 |  | }
726 |  |
727 | 1 | public Object visit(ASTEnumDeclaration node, Object data) {
728 |  | // just skip Enums
729 | 1 | return data;
730 |  | }
731 |  |
732 |  | /**
733 |  | * This check must be evaluated independelty for each class. Inner classses
734 |  | * get their own EvalPackage in order to perform independent evaluation.
735 |  | */
736 | 9 | public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
737 | 9 | if (!node.isInterface()) {
738 | 9 | return visitClassDec(node, data);
739 |  | } else {
740 | 0 | putEvalPackage(nullEvalPackage);
741 | 0 | Object o = super.visit(node, data);//interface may have inner classes, possible? if not just skip whole interface
742 | 0 | removeCurrentEvalPackage();
743 | 0 | return o;
744 |  | }
745 |  | }
746 |  |
747 |  |
748 |  | /**
749 |  | * Non-private constructor's methods are added to a list for later safety
750 |  | * evaluation. Non-private constructor's calls on private constructors
751 |  | * are added to a list for later safety evaluation. Private constructors
752 |  | * are added to a list so their safety to be called can be later evaluated.
753 |  | * <p/>
754 |  | * Note: We are not checking private constructor's calls on non-private
755 |  | * constructors because all non-private constructors will be evaluated for
756 |  | * safety anyway. This means we wont flag a private constructor as unsafe
757 |  | * just because it calls an unsafe public constructor. We want to show only
758 |  | * 1 instance of an error, and this would be 2 instances of the same error.
759 |  | *
760 |  | * @todo eliminate the redundency
761 |  | */
762 | 10 | public Object visit(ASTConstructorDeclaration node, Object data) {
763 | 10 | if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class
764 | 10 | List calledMethodsOfConstructor = new ArrayList();
765 | 10 | ConstructorHolder ch = new ConstructorHolder(node);
766 | 10 | addCalledMethodsOfNode(node, calledMethodsOfConstructor, getCurrentEvalPackage().m_ClassName);
767 | 10 | if (!node.isPrivate()) {
768 |  | //these calledMethods are what we will evaluate for being called badly
769 | 9 | getCurrentEvalPackage().calledMethods.addAll(calledMethodsOfConstructor);
770 |  | //these called private constructors are what we will evaluate for being called badly
771 |  | //we add all constructors invoked by non-private constructors
772 |  | //but we are only interested in the private ones. We just can't tell the difference here
773 | 9 | ASTExplicitConstructorInvocation eci = ch.getASTExplicitConstructorInvocation();
774 | 9 | if (eci != null && eci.isThis()) {
775 | 1 | getCurrentEvalPackage().calledConstructors.add(ch.getCalledConstructor());
776 |  | }
777 |  | } else {
778 |  | //add all private constructors to list for later evaluation on if they are safe to call from another constructor
779 |  | //store this constructorHolder for later evaluation
780 | 1 | getCurrentEvalPackage().allPrivateConstructorsOfClass.put(ch, calledMethodsOfConstructor);
781 |  | }
782 |  | }
783 | 10 | return super.visit(node, data);
784 |  | }
785 |  |
786 |  | /**
787 |  | * Create a MethodHolder to hold the method.
788 |  | * Store the MethodHolder in the Map as the key
789 |  | * Store each method called by the current method as a List in the Map as the Object
790 |  | */
791 | 10 | public Object visit(ASTMethodDeclarator node, Object data) {
792 | 10 | if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class
793 | 10 | AccessNode parent = (AccessNode) node.jjtGetParent();
794 | 10 | MethodHolder h = new MethodHolder(node);
795 | 10 | if (!parent.isAbstract() && !parent.isPrivate() && !parent.isStatic() && !parent.isFinal()) { //Skip abstract methods, have a separate rule for that
796 | 7 | h.setDangerous();//this method is overridable
797 | 7 | ASTMethodDeclaration decl = (ASTMethodDeclaration) node.getFirstParentOfType(ASTMethodDeclaration.class);
798 | 7 | h.setCalledMethod(decl.getMethodName());
799 |  | }
800 | 10 | List l = new ArrayList();
801 | 10 | addCalledMethodsOfNode((SimpleNode) parent, l, getCurrentEvalPackage().m_ClassName);
802 | 10 | getCurrentEvalPackage().allMethodsOfClass.put(h, l);
803 |  | }
804 | 10 | return super.visit(node, data);
805 |  | }
806 |  |
807 |  |
808 | 10 | private static void addCalledMethodsOfNode(AccessNode node, List calledMethods, String className) {
809 | 10 | List expressions = new ArrayList();
810 | 10 | node.findChildrenOfType(ASTPrimaryExpression.class, expressions, false);
811 | 10 | addCalledMethodsOfNodeImpl(expressions, calledMethods, className);
812 |  | }
813 |  |
814 |  | /**
815 |  | * Adds all methods called on this instance from within this Node.
816 |  | */
817 | 10 | private static void addCalledMethodsOfNode(SimpleNode node, List calledMethods, String className) {
818 | 10 | List expressions = new ArrayList();
819 | 10 | node.findChildrenOfType(ASTPrimaryExpression.class, expressions);
820 | 10 | addCalledMethodsOfNodeImpl(expressions, calledMethods, className);
821 |  | }
822 |  |
823 | 20 | private static void addCalledMethodsOfNodeImpl(List expressions, List calledMethods, String className) {
824 | 20 | for (Iterator it = expressions.iterator(); it.hasNext();) {
825 | 13 | ASTPrimaryExpression ape = (ASTPrimaryExpression) it.next();
826 | 13 | MethodInvocation meth = findMethod(ape, className);
827 | 13 | if (meth != null) {
828 |  | //System.out.println("Adding call " + methName);
829 | 9 | calledMethods.add(meth);
830 |  | }
831 |  | }
832 |  | }
833 |  |
834 |  | /**
835 |  | * @return A method call on the class passed in, or null if no method call
836 |  | * is found.
837 |  | * @todo Need a better way to match the class and package name to the actual
838 |  | * method being called.
839 |  | */
840 | 13 | private static MethodInvocation findMethod(ASTPrimaryExpression node, String className) {
841 | 13 | if (node.jjtGetNumChildren() > 0
842 |  | && node.jjtGetChild(0).jjtGetNumChildren() > 0
843 |  | && node.jjtGetChild(0).jjtGetChild(0) instanceof ASTLiteral) {
844 | 2 | return null;
845 |  | }
846 | 11 | MethodInvocation meth = MethodInvocation.getMethod(node);
847 | 11 | boolean found = false;
848 |  | // if(meth != null){
849 |  | // meth.show();
850 |  | // }
851 | 11 | if (meth != null) {
852 |  | //if it's a call on a variable, or on its superclass ignore it.
853 | 9 | if ((meth.getReferenceNames().size() == 0) && !meth.isSuper()) {
854 |  | //if this list does not contain our class name, then its not referencing our class
855 |  | //this is a cheezy test... but it errs on the side of less false hits.
856 | 9 | List packClass = meth.getQualifierNames();
857 | 9 | if (!packClass.isEmpty()) {
858 | 0 | for (Iterator it = packClass.iterator(); it.hasNext();) {
859 | 0 | String name = (String) it.next();
860 | 0 | if (name.equals(className)) {
861 | 0 | found = true;
862 | 0 | break;
863 |  | }
864 |  | }
865 |  | } else {
866 | 9 | found = true;
867 |  | }
868 |  | }
869 |  | }
870 |  |
871 | 11 | return found ? meth : null;
872 |  | }
873 |  |
874 |  | /**
875 |  | * ASTPrimaryPrefix has name in child node of ASTName
876 |  | */
877 | 18 | private static String getNameFromPrefix(ASTPrimaryPrefix node) {
878 | 18 | String name = null;
879 |  | //should only be 1 child, if more I need more knowledge
880 | 18 | if (node.jjtGetNumChildren() == 1) { //safety check
881 | 18 | Node nnode = node.jjtGetChild(0);
882 | 18 | if (nnode instanceof ASTName) { //just as easy as null check and it should be an ASTName anyway
883 | 18 | name = ((ASTName) nnode).getImage();
884 |  | }
885 |  | }
886 | 18 | return name;
887 |  | }
888 |  |
889 |  | }