View Javadoc

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