View Javadoc

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