View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.strings;
5   
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
11  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
13  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
15  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
16  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
17  import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTName;
19  import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
20  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
21  import net.sourceforge.pmd.lang.java.ast.ASTType;
22  import net.sourceforge.pmd.lang.java.ast.AccessNode;
23  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
24  import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
25  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
26  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
27  
28  /**
29   * How this rule works:
30   * find additive expressions: +
31   * check that the addition is between anything other than two literals
32   * if true and also the parent is StringBuffer constructor or append,
33   * report a violation.
34   *
35   * @author mgriffa
36   */
37  public class InefficientStringBufferingRule extends AbstractJavaRule {
38  
39      @Override
40      public Object visit(ASTAdditiveExpression node, Object data) {
41          ASTBlockStatement bs = node.getFirstParentOfType(ASTBlockStatement.class);
42          if (bs == null) {
43              return data;
44          }
45  
46          int immediateLiterals = 0;
47          int immediateStringLiterals = 0;
48          List<ASTLiteral> nodes = node.findDescendantsOfType(ASTLiteral.class);
49          for (ASTLiteral literal: nodes) {
50              if (literal.getNthParent(3) instanceof ASTAdditiveExpression) {
51                  immediateLiterals++;
52                  if (literal.isStringLiteral()) {
53                  	immediateStringLiterals++;
54                  }
55              }
56              if (literal.isIntLiteral() || literal.isFloatLiteral()) {
57          	return data;
58              }
59          }
60  
61          if (immediateLiterals > 1) {
62              return data;
63          }
64  
65          // if literal + public static final, return
66          List<ASTName> nameNodes = node.findDescendantsOfType(ASTName.class);
67          for (ASTName name: nameNodes) {
68              if (name.getNameDeclaration() != null && name.getNameDeclaration() instanceof VariableNameDeclaration) {
69                  VariableNameDeclaration vnd = (VariableNameDeclaration)name.getNameDeclaration();
70                  AccessNode accessNodeParent = vnd.getAccessNodeParent();
71                  if (accessNodeParent.isFinal() && accessNodeParent.isStatic()) {
72                      return data;
73                  }
74              }
75          }
76          
77          // if literal primitive type and not strings variables, then return
78          boolean stringFound = false;
79          for (ASTName name: nameNodes) {
80          	if (!isPrimitiveType(name) && isStringType(name)) {
81          		stringFound = true;
82          		break;
83          	}
84          }
85          if (!stringFound && immediateStringLiterals == 0) {
86          	return data;
87          }
88  
89          if (bs.isAllocation()) {
90              for (Iterator<ASTName> iterator = nameNodes.iterator(); iterator.hasNext();) {
91              	ASTName name = iterator.next();
92      			if (!name.getImage().endsWith("length")) {
93      				break;
94      			} else if (!iterator.hasNext()) {
95      				return data;	//All names end with length
96      			}
97      		}
98  
99              if (isAllocatedStringBuffer(node)) {
100                 addViolation(data, node);
101             }
102         } else if (isInStringBufferOperation(node, 6, "append")) {
103             addViolation(data, node);
104         }
105         return data;
106     }
107 
108     private boolean isStringType(ASTName name) {
109     	ASTType type = getTypeNode(name);
110     	if (type != null) {
111     		List<ASTClassOrInterfaceType> types = type.findDescendantsOfType(ASTClassOrInterfaceType.class);
112     		if (!types.isEmpty()) {
113     			ASTClassOrInterfaceType typeDeclaration = types.get(0);
114     			if (String.class == typeDeclaration.getType() || "String".equals(typeDeclaration.getImage())) {
115     				return true;
116     			}
117     		}
118     	}
119 		return false;
120 	}
121 
122 	private boolean isPrimitiveType(ASTName name) {
123 		ASTType type = getTypeNode(name);
124 		if (type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty()) {
125 			return true;
126 		}
127 		return false;
128 	}
129 	
130 	private ASTType getTypeNode(ASTName name) {
131     	if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
132     	    VariableNameDeclaration vnd = (VariableNameDeclaration) name.getNameDeclaration();
133     		if (vnd.getAccessNodeParent() instanceof ASTLocalVariableDeclaration) {
134     			ASTLocalVariableDeclaration l = (ASTLocalVariableDeclaration)vnd.getAccessNodeParent();
135     			return l.getTypeNode();
136     		} else if (vnd.getAccessNodeParent() instanceof ASTFormalParameter) {
137     			ASTFormalParameter p = (ASTFormalParameter) vnd.getAccessNodeParent();
138     			return p.getTypeNode();
139     		}
140     	}
141 		return null;
142 	}
143 
144 	protected static boolean isInStringBufferOperation(Node node, int length, String methodName) {
145         if (!(node.getNthParent(length) instanceof ASTStatementExpression)) {
146             return false;
147         }
148         ASTStatementExpression s = node.getFirstParentOfType(ASTStatementExpression.class);
149         if (s == null) {
150             return false;
151         }
152         ASTName n = s.getFirstDescendantOfType(ASTName.class);
153         if (n == null || n.getImage().indexOf(methodName) == -1 || !(n.getNameDeclaration() instanceof TypedNameDeclaration)) {
154             return false;
155         }
156 
157         // TODO having to hand-code this kind of dredging around is ridiculous
158         // we need something to support this in the framework
159         // but, "for now" (tm):
160         // if more than one arg to append(), skip it
161         ASTArgumentList argList = s.getFirstDescendantOfType(ASTArgumentList.class);
162         if (argList == null || argList.jjtGetNumChildren() > 1) {
163             return false;
164         }
165         return TypeHelper.isEither((TypedNameDeclaration)n.getNameDeclaration(), StringBuffer.class, StringBuilder.class);
166     }
167 
168     private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
169         ASTAllocationExpression ao = node.getFirstParentOfType(ASTAllocationExpression.class);
170         if (ao == null) {
171             return false;
172         }
173         // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal:  t = new int[ nWords+wordcount+1 ];
174         ASTClassOrInterfaceType an = ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
175         return an != null && TypeHelper.isEither(an, StringBuffer.class, StringBuilder.class);
176     }
177 }
178