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