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