View Javadoc

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