View Javadoc

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