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