View Javadoc

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