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