/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.rules; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.ast.ASTArguments; import net.sourceforge.pmd.ast.ASTClassDeclaration; import net.sourceforge.pmd.ast.ASTCompilationUnit; import net.sourceforge.pmd.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.ast.ASTExplicitConstructorInvocation; import net.sourceforge.pmd.ast.ASTInterfaceDeclaration; import net.sourceforge.pmd.ast.ASTLiteral; import net.sourceforge.pmd.ast.ASTMethodDeclarator; import net.sourceforge.pmd.ast.ASTName; import net.sourceforge.pmd.ast.ASTNestedClassDeclaration; import net.sourceforge.pmd.ast.ASTNestedInterfaceDeclaration; import net.sourceforge.pmd.ast.ASTPrimaryExpression; import net.sourceforge.pmd.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.ast.ASTPrimarySuffix; import net.sourceforge.pmd.ast.ASTUnmodifiedClassDeclaration; import net.sourceforge.pmd.ast.AccessNode; import net.sourceforge.pmd.ast.Node; import net.sourceforge.pmd.ast.SimpleNode; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Searches through all methods and constructors called from constructors. It * marks as dangerous any call to overridable methods from non-private * constructors. It marks as dangerous any calls to dangerous private constructors * from non-private constructors. * * * @todo match parameter types. Agressive strips off any package names. Normal * compares the names as is. * * @todo What about interface declarations which can have internal classes * * @author CL Gilbert (dnoyeb@users.sourceforge.net) */ public final class ConstructorCallsOverridableMethodRule extends net.sourceforge.pmd.AbstractRule { /** * 2: method(); * ASTPrimaryPrefix * ASTName image = "method" * ASTPrimarySuffix * *ASTArguments * 3: a.method(); * ASTPrimaryPrefix -> * ASTName image = "a.method" ??? * ASTPrimarySuffix -> () * ASTArguments * 3: this.method(); * ASTPrimaryPrefix -> this image=null * ASTPrimarySuffix -> method * ASTPrimarySuffix -> () * ASTArguments * * super.method(); * ASTPrimaryPrefix -> image = "method" * ASTPrimarySuffix -> image = null * ASTArguments -> * * super.a.method(); * ASTPrimaryPrefix -> image = "a" * ASTPrimarySuffix -> image = "method" * ASTPrimarySuffix -> image = null * ASTArguments -> * * 4: this.a.method(); * ASTPrimaryPrefix -> image = null * ASTPrimarySuffix -> image = "a" * ASTPrimarySuffix -> image = "method" * ASTPrimarySuffix -> * ASTArguments * * 4: ClassName.this.method(); * ASTPrimaryPrefix * ASTName image = "ClassName" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> image = "method" * ASTPrimarySuffix -> () * ASTArguments * 5: ClassName.this.a.method(); * ASTPrimaryPrefix * ASTName image = "ClassName" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> image="a" * ASTPrimarySuffix -> image="method" * ASTPrimarySuffix -> () * ASTArguments * 5: Package.ClassName.this.method(); * ASTPrimaryPrefix * ASTName image ="Package.ClassName" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> image="method" * ASTPrimarySuffix -> () * ASTArguments * 6: Package.ClassName.this.a.method(); * ASTPrimaryPrefix * ASTName image ="Package.ClassName" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> a * ASTPrimarySuffix -> method * ASTPrimarySuffix -> () * ASTArguments * 5: OuterClass.InnerClass.this.method(); * ASTPrimaryPrefix * ASTName image = "OuterClass.InnerClass" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> method * ASTPrimarySuffix -> () * ASTArguments * 6: OuterClass.InnerClass.this.a.method(); * ASTPrimaryPrefix * ASTName image = "OuterClass.InnerClass" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> a * ASTPrimarySuffix -> method * ASTPrimarySuffix -> () * ASTArguments * * OuterClass.InnerClass.this.a.method().method().method(); * ASTPrimaryPrefix * ASTName image = "OuterClass.InnerClass" * ASTPrimarySuffix -> this image=null * ASTPrimarySuffix -> a image='a' * ASTPrimarySuffix -> method image='method' * ASTPrimarySuffix -> () image=null * ASTArguments * ASTPrimarySuffix -> method image='method' * ASTPrimarySuffix -> () image=null * ASTArguments * ASTPrimarySuffix -> method image='method' * ASTPrimarySuffix -> () image=null * ASTArguments * * 3..n: Class.InnerClass[0].InnerClass[n].this.method(); * ASTPrimaryPrefix * ASTName image = "Class[0]..InnerClass[n]" * ASTPrimarySuffix -> image=null * ASTPrimarySuffix -> method * ASTPrimarySuffix -> () * ASTArguments * * super.aMethod(); * ASTPrimaryPrefix -> aMethod * ASTPrimarySuffix -> () * * Evaluate right to left * */ private static class MethodInvocation { private String m_Name; private ASTPrimaryExpression m_Ape; private List m_ReferenceNames; private List m_QualifierNames; private int m_ArgumentSize; private boolean m_Super; private MethodInvocation(ASTPrimaryExpression ape, List qualifierNames, List referenceNames, String name, int argumentSize, boolean superCall) { m_Ape = ape; m_QualifierNames = qualifierNames; m_ReferenceNames = referenceNames; m_Name = name; m_ArgumentSize = argumentSize; m_Super = superCall; } public boolean isSuper() { return m_Super; } public String getName() { return m_Name; } public int getArgumentCount() { return m_ArgumentSize; } public List getReferenceNames() { return m_ReferenceNames;//new ArrayList(variableNames); } public List getQualifierNames() { return m_QualifierNames; } public ASTPrimaryExpression getASTPrimaryExpression() { return m_Ape; } public static MethodInvocation getMethod(ASTPrimaryExpression node) { MethodInvocation meth = null; int i = node.jjtGetNumChildren(); if (i > 1) {//should always be at least 2, probably can eliminate this check //start at end which is guaranteed, work backwards Node lastNode = node.jjtGetChild(i - 1); if ((lastNode.jjtGetNumChildren() == 1) && (lastNode.jjtGetChild(0) instanceof ASTArguments)) { //could be ASTExpression for instance 'a[4] = 5'; //start putting method together // System.out.println("Putting method together now"); List varNames = new ArrayList(); List packagesAndClasses = new ArrayList(); //look in JLS for better name here; String methodName = null; ASTArguments args = (ASTArguments) lastNode.jjtGetChild(0); int numOfArguments = args.getArgumentCount(); boolean superFirst = false; int thisIndex = -1; FIND_SUPER_OR_THIS: { //search all nodes except last for 'this' or 'super'. will be at: (node 0 | node 1 | nowhere) //this is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments) //this is an ASTPrimaryPrefix with a null image and an ASTName that has a null image //super is an ASTPrimarySuffix with a null image and does not have child (which will be of type ASTArguments) //super is an ASTPrimaryPrefix with a non-null image for (int x = 0; x < i - 1; x++) { Node child = node.jjtGetChild(x); if (child instanceof ASTPrimarySuffix) { //check suffix type match ASTPrimarySuffix child2 = (ASTPrimarySuffix) child; // String name = getNameFromSuffix((ASTPrimarySuffix)child); // System.out.println("found name suffix of : " + name); if (child2.getImage() == null && child2.jjtGetNumChildren() == 0) { thisIndex = x; break; } //could be super, could be this. currently we cant tell difference so we miss super when //XYZ.ClassName.super.method(); //still works though. } else if (child instanceof ASTPrimaryPrefix) { //check prefix type match ASTPrimaryPrefix child2 = (ASTPrimaryPrefix) child; if (getNameFromPrefix(child2) == null) { if (child2.getImage() == null) { thisIndex = x; break; } else {//happens when super is used [super.method(): image = 'method'] superFirst = true; thisIndex = x; //the true super is at an unusable index because super.method() has only 2 nodes [method=0,()=1] //as opposed to the 3 you might expect and which this.method() actually has. [this=0,method=1.()=2] break; } } } // else{ // System.err.println("Bad Format error"); //throw exception, quit evaluating this compilation node // } } } if (thisIndex != -1) { // System.out.println("Found this or super: " + thisIndex); //Hack that must be removed if and when the patters of super.method() begins to logically match the rest of the patterns !!! if (superFirst) { //this is when super is the first node of statement. no qualifiers, all variables or method // System.out.println("super first"); FIRSTNODE:{ ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0); String name = child.getImage();//special case if (i == 2) { //last named node = method name methodName = name; } else { //not the last named node so its only var name varNames.add(name); } } OTHERNODES:{ //variables for (int x = 1; x < i - 1; x++) { Node child = node.jjtGetChild(x); ASTPrimarySuffix ps = (ASTPrimarySuffix) child; if (ps.isArguments() == false) { String name = ((ASTPrimarySuffix) child).getImage(); if (x == i - 2) {//last node methodName = name; } else {//not the last named node so its only var name varNames.add(name); } } } } } else {//not super call FIRSTNODE:{ if (thisIndex == 1) {//qualifiers in node 0 ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0); String toParse = getNameFromPrefix(child); // System.out.println("parsing for class/package names in : " + toParse); java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, "."); while (st.hasMoreTokens()) { packagesAndClasses.add(st.nextToken()); } } } OTHERNODES:{ //other methods called in this statement are grabbed here //this is at 0, then no Qualifiers //this is at 1, the node 0 contains qualifiers for (int x = thisIndex + 1; x < i - 1; x++) {//everything after this is var name or method name ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x); if (child.isArguments() == false) { //skip the () of method calls String name = getNameFromSuffix(child); // System.out.println("Found suffix: " + suffixName); if (x == i - 2) { methodName = name; } else { varNames.add(name); } } } } } } else { //if no this or super found, everything is method name or variable //System.out.println("no this found:"); FIRSTNODE:{ //variable names are in the prefix + the first method call [a.b.c.x()] ASTPrimaryPrefix child = (ASTPrimaryPrefix) node.jjtGetChild(0); String toParse = getNameFromPrefix(child); // System.out.println("parsing for var names in : " + toParse); java.util.StringTokenizer st = new java.util.StringTokenizer(toParse, "."); while (st.hasMoreTokens()) { String value = st.nextToken(); if (!st.hasMoreTokens()) { if (i == 2) {//if this expression is 2 nodes long, then the last part of prefix is method name methodName = value; } else { varNames.add(value); } } else { //variable name varNames.add(value); } } } OTHERNODES:{ //other methods called in this statement are grabbed here for (int x = 1; x < i - 1; x++) { ASTPrimarySuffix child = (ASTPrimarySuffix) node.jjtGetChild(x); if (child.isArguments() == false) { String name = getNameFromSuffix(child); if (x == i - 2) { methodName = name; } else { varNames.add(name); } } } } } meth = new MethodInvocation(node, packagesAndClasses, varNames, methodName, numOfArguments, superFirst); } } return meth; } public void show() { System.out.println("<MethodInvocation>"); List pkg = getQualifierNames(); System.out.println(" <Qualifiers>"); for (Iterator it = pkg.iterator(); it.hasNext();) { String name = (String) it.next(); System.out.println(" " + name); } System.out.println(" </Qualifiers>"); System.out.println(" <Super>" + isSuper() + "</Super>"); List vars = getReferenceNames(); System.out.println(" <References>"); for (Iterator it = vars.iterator(); it.hasNext();) { String name = (String) it.next(); System.out.println(" " + name); } System.out.println(" </References>"); System.out.println(" <Name>" + getName() + "</Name>"); System.out.println("</MethodInvocation>"); } } private final class ConstructorInvocation { private ASTExplicitConstructorInvocation m_Eci; private String name; private int count = 0; public ConstructorInvocation(ASTExplicitConstructorInvocation eci) { m_Eci = eci; List l = new ArrayList(); eci.findChildrenOfType(ASTArguments.class, l); if (l.size() > 0) { ASTArguments aa = (ASTArguments) l.get(0); count = aa.getArgumentCount(); } name = eci.getImage(); } public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() { return m_Eci; } public int getArgumentCount() { return count; } public String getName() { return name; } } private final class MethodHolder { private ASTMethodDeclarator m_Amd; private boolean m_Dangerous = false; public MethodHolder(ASTMethodDeclarator amd) { m_Amd = amd; } public ASTMethodDeclarator getASTMethodDeclarator() { return m_Amd; } public boolean isDangerous() { return m_Dangerous; } public void setDangerous(boolean dangerous) { m_Dangerous = dangerous; } } private final class ConstructorHolder { private ASTConstructorDeclaration m_Cd; private boolean m_Dangerous = false; private ConstructorInvocation m_Ci; private boolean m_CiInitialized = false; public ConstructorHolder(ASTConstructorDeclaration cd) { m_Cd = cd; } public ASTConstructorDeclaration getASTConstructorDeclaration() { return m_Cd; } public ConstructorInvocation getCalledConstructor() { if (m_CiInitialized == false) { initCI(); } return m_Ci; } public ASTExplicitConstructorInvocation getASTExplicitConstructorInvocation() { ASTExplicitConstructorInvocation eci = null; if (m_CiInitialized == false) { initCI(); } if (m_Ci != null) { eci = m_Ci.getASTExplicitConstructorInvocation(); } return eci; } private void initCI() { List expressions = new ArrayList(); m_Cd.findChildrenOfType(ASTExplicitConstructorInvocation.class, expressions); //only 1... if (expressions.size() > 0) { ASTExplicitConstructorInvocation eci = (ASTExplicitConstructorInvocation) expressions.get(0); m_Ci = new ConstructorInvocation(eci); //System.out.println("Const call " + eci.getImage()); //super or this??? } m_CiInitialized = true; } public boolean isDangerous() { return m_Dangerous; } public void setDangerous(boolean dangerous) { m_Dangerous = dangerous; } } /** * 1 package per class. holds info for evaluating a single class. */ private static class EvalPackage { public EvalPackage() { } public EvalPackage(String className) { m_ClassName = className; calledMethods = new ArrayList();//meths called from constructor allMethodsOfClass = new HashMap(); calledConstructors = new ArrayList();//all constructors called from constructor allPrivateConstructorsOfClass = new HashMap(); } public String m_ClassName; public List calledMethods; public Map allMethodsOfClass; public List calledConstructors; public Map allPrivateConstructorsOfClass; } private static final class NullEvalPackage extends EvalPackage { public NullEvalPackage() { m_ClassName = ""; calledMethods = Collections.EMPTY_LIST; allMethodsOfClass = Collections.EMPTY_MAP; calledConstructors = Collections.EMPTY_LIST; allPrivateConstructorsOfClass = Collections.EMPTY_MAP; } } private static final NullEvalPackage nullEvalPackage = new NullEvalPackage(); /** * 1 package per class. */ private final List evalPackages = new ArrayList();//could use java.util.Stack private EvalPackage getCurrentEvalPackage() { return (EvalPackage) evalPackages.get(evalPackages.size() - 1); } /** * Adds and evaluation package and makes it current */ private void putEvalPackage(EvalPackage ep) { evalPackages.add(ep); } private void removeCurrentEvalPackage() { evalPackages.remove(evalPackages.size() - 1); } private void clearEvalPackages() { evalPackages.clear(); } /** * This check must be evaluated independelty for each class. Inner classses * get their own EvalPackage in order to perform independent evaluation. */ private Object visitClassDec(AccessNode node, Object data) { String className = ((ASTUnmodifiedClassDeclaration) node.jjtGetChild(0)).getImage(); // System.out.println("Class is " + className); //evaluate each level independently if (!node.isFinal() && !node.isStatic()) { putEvalPackage(new EvalPackage(className)); } else { putEvalPackage(nullEvalPackage); } //store any errors caught from other passes. if (node instanceof ASTClassDeclaration) { super.visit((ASTClassDeclaration) node, data); } else { super.visit((ASTNestedClassDeclaration) node, data); } //skip this class if it has no evaluation package if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) { //evaluate danger of all methods in class while (evaluateDangerOfMethods(getCurrentEvalPackage().allMethodsOfClass) == true) ; //evaluate danger of constructors evaluateDangerOfConstructors1(getCurrentEvalPackage().allPrivateConstructorsOfClass, getCurrentEvalPackage().allMethodsOfClass.keySet()); while (evaluateDangerOfConstructors2(getCurrentEvalPackage().allPrivateConstructorsOfClass) == true) ; //get each method called on this object from a non-private constructor, if its dangerous flag it for (Iterator it = getCurrentEvalPackage().calledMethods.iterator(); it.hasNext();) { MethodInvocation meth = (MethodInvocation) it.next(); //check against each dangerous method in class for (Iterator it2 = getCurrentEvalPackage().allMethodsOfClass.keySet().iterator(); it2.hasNext();) { MethodHolder h = (MethodHolder) it2.next(); if (h.isDangerous()) { String methName = h.getASTMethodDeclarator().getImage(); int count = h.getASTMethodDeclarator().getParameterCount(); if (meth.getName().equals(methName) && (meth.getArgumentCount() == count)) { //bad call RuleContext ctx = (RuleContext) data; ctx.getReport().addRuleViolation(createRuleViolation(ctx, meth.getASTPrimaryExpression().getBeginLine())); } } } } //get each unsafe private constructor, and check if its called from any non private constructors for (Iterator privConstIter = getCurrentEvalPackage().allPrivateConstructorsOfClass.keySet().iterator(); privConstIter.hasNext();) { ConstructorHolder ch = (ConstructorHolder) privConstIter.next(); if (ch.isDangerous()) { //if its dangerous check if its called from any non-private constructors //System.out.println("visitClassDec Evaluating dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params"); int paramCount = ch.getASTConstructorDeclaration().getParameterCount(); for (Iterator calledConstIter = getCurrentEvalPackage().calledConstructors.iterator(); calledConstIter.hasNext();) { ConstructorInvocation ci = (ConstructorInvocation) calledConstIter.next(); if (ci.getArgumentCount() == paramCount) { //match name super / this !? RuleContext ctx = (RuleContext) data; ctx.getReport().addRuleViolation(createRuleViolation(ctx, ci.getASTExplicitConstructorInvocation().getBeginLine())); } } } } } //finished evaluating this class, move up a level removeCurrentEvalPackage(); return data; } /** * Check the methods called on this class by each of the methods on this * class. If a method calls an unsafe method, mark the calling method as * unsafe. This changes the list of unsafe methods which necessitates * another pass. Keep passing until you make a clean pass in which no * methods are changed to unsafe. * For speed it is possible to limit the number of passes. * * Impossible to tell type of arguments to method, so forget method matching * on types. just use name and num of arguments. will be some false hits, * but oh well. * * @todo investigate limiting the number of passes through config. */ private boolean evaluateDangerOfMethods(Map classMethodMap) { //check each method if it calls overridable method boolean found = false; for (Iterator methodsIter = classMethodMap.keySet().iterator(); methodsIter.hasNext();) { MethodHolder h = (MethodHolder) methodsIter.next(); List calledMeths = (List) classMethodMap.get(h); for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && (h.isDangerous() == false);) { //if this method matches one of our dangerous methods, mark it dangerous MethodInvocation meth = (MethodInvocation) calledMethsIter.next(); //System.out.println("Called meth is " + meth); for (Iterator innerMethsIter = classMethodMap.keySet().iterator(); innerMethsIter.hasNext();) { //need to skip self here h == h3 MethodHolder h3 = (MethodHolder) innerMethsIter.next(); if (h3.isDangerous()) { String matchMethodName = h3.getASTMethodDeclarator().getImage(); int matchMethodParamCount = h3.getASTMethodDeclarator().getParameterCount(); //System.out.println("matchint " + matchMethodName + " to " + methName); if (matchMethodName.equals(meth.getName()) && (matchMethodParamCount == meth.getArgumentCount())) { h.setDangerous(true); found = true; break; } } } } } return found; } /** * marks constructors dangerous if they call any dangerous methods * Requires only a single pass as methods are already marked * @todo optimize by having methods already evaluated somehow!? */ private void evaluateDangerOfConstructors1(Map classConstructorMap, Set evaluatedMethods) { //check each constructor in the class for (Iterator constIter = classConstructorMap.keySet().iterator(); constIter.hasNext();) { ConstructorHolder ch = (ConstructorHolder) constIter.next(); if (!ch.isDangerous()) {//if its not dangerous then evaluate if it should be //if it calls dangerous method mark it as dangerous List calledMeths = (List) classConstructorMap.get(ch); //check each method it calls for (Iterator calledMethsIter = calledMeths.iterator(); calledMethsIter.hasNext() && !ch.isDangerous();) {//but thee are diff objects which represent same thing but were never evaluated, they need reevaluation MethodInvocation meth = (MethodInvocation) calledMethsIter.next();//CCE String methName = meth.getName(); int methArgCount = meth.getArgumentCount(); //check each of the already evaluated methods: need to optimize this out for (Iterator evaldMethsIter = evaluatedMethods.iterator(); evaldMethsIter.hasNext();) { MethodHolder h = (MethodHolder) evaldMethsIter.next(); if (h.isDangerous()) { String matchName = h.getASTMethodDeclarator().getImage(); int matchParamCount = h.getASTMethodDeclarator().getParameterCount(); if (methName.equals(matchName) && (methArgCount == matchParamCount)) { ch.setDangerous(true); //System.out.println("evaluateDangerOfConstructors1 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params"); break; } } } } } } } /** * Constructor map should contain a key for each private constructor, and * maps to a List which contains all called constructors of that key. * marks dangerous if call dangerous private constructor * we ignore all non-private constructors here. That is, the map passed in * should not contain any non-private constructors. * we return boolean in order to limit the number of passes through this method * but it seems as if we can forgo that and just process it till its done. */ private boolean evaluateDangerOfConstructors2(Map classConstructorMap) { boolean found = false;//triggers on danger state change //check each constructor in the class for (Iterator constIter = classConstructorMap.keySet().iterator(); constIter.hasNext();) { ConstructorHolder ch = (ConstructorHolder) constIter.next(); ConstructorInvocation calledC = ch.getCalledConstructor(); if (calledC == null || ch.isDangerous()) { continue; } //if its not dangerous then evaluate if it should be //if it calls dangerous constructor mark it as dangerous int cCount = calledC.getArgumentCount(); for (Iterator innerConstIter = classConstructorMap.keySet().iterator(); innerConstIter.hasNext() && !ch.isDangerous();) { //forget skipping self because that introduces another check for each, but only 1 hit ConstructorHolder h2 = (ConstructorHolder) innerConstIter.next(); if (h2.isDangerous()) { int matchConstArgCount = h2.getASTConstructorDeclaration().getParameterCount(); if (matchConstArgCount == cCount) { ch.setDangerous(true); found = true; //System.out.println("evaluateDangerOfConstructors2 setting dangerous constructor with " + ch.getASTConstructorDeclaration().getParameterCount() + " params"); } } } } return found; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //The Visited Methods /** * Work on each file independently. */ public Object visit(ASTCompilationUnit node, Object data) { clearEvalPackages(); // try { return super.visit(node, data); // } // catch(Exception e){ // e.printStackTrace(); // } } //for testing only // public Object visit(ASTPackageDeclaration node, Object data){ // System.out.println("package= " + ((ASTName)node.jjtGetChild(0)).getImage()); // return super.visit(node,data); // } /** * This check must be evaluated independelty for each class. Inner classses * get their own EvalPackage in order to perform independent evaluation. */ public Object visit(ASTClassDeclaration node, Object data) { return visitClassDec(node, data); } public Object visit(ASTNestedClassDeclaration node, Object data) { return visitClassDec(node, data); } public Object visit(ASTInterfaceDeclaration node, Object data) { putEvalPackage(nullEvalPackage); Object o = super.visit(node, data);//interface may have inner classes, possible? if not just skip whole interface removeCurrentEvalPackage(); return o; } public Object visit(ASTNestedInterfaceDeclaration node, Object data) { putEvalPackage(nullEvalPackage); Object o = super.visit(node, data);//interface may have inner classes, possible? if not just skip whole interface removeCurrentEvalPackage(); return o; } /** * Non-private constructor's methods are added to a list for later safety * evaluation. Non-private constructor's calls on private constructors * are added to a list for later safety evaluation. Private constructors * are added to a list so their safety to be called can be later evaluated. * * Note: We are not checking private constructor's calls on non-private * constructors because all non-private constructors will be evaluated for * safety anyway. This means we wont flag a private constructor as unsafe * just because it calls an unsafe public constructor. We want to show only * 1 instance of an error, and this would be 2 instances of the same error. * * @todo eliminate the redundency */ public Object visit(ASTConstructorDeclaration node, Object data) { if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class List calledMethodsOfConstructor = new ArrayList(); ConstructorHolder ch = new ConstructorHolder(node); addCalledMethodsOfNode(node, calledMethodsOfConstructor, getCurrentEvalPackage().m_ClassName); if (!node.isPrivate()) { //these calledMethods are what we will evaluate for being called badly getCurrentEvalPackage().calledMethods.addAll(calledMethodsOfConstructor); //these called private constructors are what we will evaluate for being called badly //we add all constructors invoked by non-private constructors //but we are only interested in the private ones. We just can't tell the difference here ASTExplicitConstructorInvocation eci = ch.getASTExplicitConstructorInvocation(); if (eci != null && eci.isThis()) { getCurrentEvalPackage().calledConstructors.add(ch.getCalledConstructor()); } } else { //add all private constructors to list for later evaluation on if they are safe to call from another constructor //store this constructorHolder for later evaluation getCurrentEvalPackage().allPrivateConstructorsOfClass.put(ch, calledMethodsOfConstructor); } } return super.visit(node, data); } /** * Create a MethodHolder to hold the method. * Store the MethodHolder in the Map as the key * Store each method called by the current method as a List in the Map as the Object */ public Object visit(ASTMethodDeclarator node, Object data) { if (!(getCurrentEvalPackage() instanceof NullEvalPackage)) {//only evaluate if we have an eval package for this class AccessNode parent = (AccessNode) node.jjtGetParent(); MethodHolder h = new MethodHolder(node); if (!parent.isPrivate() && !parent.isStatic() && !parent.isFinal()) { h.setDangerous(true);//this method is overridable } List l = new ArrayList(); addCalledMethodsOfNode((SimpleNode) parent, l, getCurrentEvalPackage().m_ClassName); getCurrentEvalPackage().allMethodsOfClass.put(h, l); } return super.visit(node, data); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //Helper methods to process visits private static void addCalledMethodsOfNode(AccessNode node, List calledMethods, String className) { List expressions = new ArrayList(); node.findChildrenOfType(ASTPrimaryExpression.class, expressions, false); addCalledMethodsOfNodeImpl(expressions, calledMethods, className); } /** * Adds all methods called on this instance from within this Node. */ private static void addCalledMethodsOfNode(SimpleNode node, List calledMethods, String className) { List expressions = new ArrayList(); node.findChildrenOfType(ASTPrimaryExpression.class, expressions); addCalledMethodsOfNodeImpl(expressions, calledMethods, className); } private static void addCalledMethodsOfNodeImpl(List expressions, List calledMethods, String className) { for (Iterator it = expressions.iterator(); it.hasNext();) { ASTPrimaryExpression ape = (ASTPrimaryExpression) it.next(); MethodInvocation meth = findMethod(ape, className); if (meth != null) { //System.out.println("Adding call " + methName); calledMethods.add(meth); } } } /** * @todo Need a better way to match the class and package name to the actual * method being called. * @return A method call on the class passed in, or null if no method call * is found. */ private static MethodInvocation findMethod(ASTPrimaryExpression node, String className) { if (node.jjtGetNumChildren() > 0 && node.jjtGetChild(0).jjtGetNumChildren() > 0 && node.jjtGetChild(0).jjtGetChild(0) instanceof ASTLiteral) { return null; } MethodInvocation meth = MethodInvocation.getMethod(node); boolean found = false; // if(meth != null){ // meth.show(); // } if (meth != null) { //if it's a call on a variable, or on its superclass ignore it. if ((meth.getReferenceNames().size() == 0) && !meth.isSuper()) { //if this list does not contain our class name, then its not referencing our class //this is a cheezy test... but it errs on the side of less false hits. List packClass = meth.getQualifierNames(); if (packClass.size() > 0) { for (Iterator it = packClass.iterator(); it.hasNext();) { String name = (String) it.next(); if (name.equals(className)) { found = true; break; } } } else { found = true; } } } if (found) { return meth; } else { return null; } } /** * ASTPrimaryPrefix has name in child node of ASTName */ private static String getNameFromPrefix(ASTPrimaryPrefix node) { String name = null; //should only be 1 child, if more I need more knowledge if (node.jjtGetNumChildren() == 1) { //safety check Node nnode = node.jjtGetChild(0); if (nnode instanceof ASTName) { //just as easy as null check and it should be an ASTName anyway name = ((ASTName) nnode).getImage(); } } return name; } /** * ASTPrimarySuffix has name in itself */ private static String getNameFromSuffix(ASTPrimarySuffix node) { return node.getImage(); } }