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 }