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.HashMap;
    7   import java.util.HashSet;
    8   import java.util.List;
    9   import java.util.Map;
    10  import java.util.Set;
    11
    12  import net.sourceforge.pmd.lang.ast.Node;
    13  import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
    14  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
    15  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
    16  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
    17  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
    18  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
    19  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
    20  import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression;
    21  import net.sourceforge.pmd.lang.java.ast.ASTName;
    22  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
    23  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
    24  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
    25  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
    26  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
    27  import net.sourceforge.pmd.lang.java.ast.ASTType;
    28  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
    29  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    30  import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
    31  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
    32
    33  /**
    34   * This rule finds StringBuffers which may have been pre-sized incorrectly
    35   *
    36   * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
    37   * @author Allan Caplan
    38   */
    39  public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule {
    40
    41      private final static Set<Class<? extends Node>> BLOCK_PARENTS;
    42
    43      static {
    44          BLOCK_PARENTS = new HashSet<Class<? extends Node>>(2);
    45          BLOCK_PARENTS.add(ASTIfStatement.class);
    46          BLOCK_PARENTS.add(ASTSwitchStatement.class);
    47      }
    48
    49      public static final int DEFAULT_BUFFER_SIZE = 16;	// as specified in StringBuffer & StringBuilder
    50
    51      @Override
    52      public Object visit(ASTVariableDeclaratorId node, Object data) {
    53          if (!TypeHelper.isEither(node.getNameDeclaration(), StringBuffer.class, StringBuilder.class)) {
    54              return data;
    55          }
    56          Node rootNode = node;
    57          int anticipatedLength = 0;
    58          int constructorLength = DEFAULT_BUFFER_SIZE;
    59
    60          constructorLength = getConstructorLength(node, constructorLength);
    61          anticipatedLength = getInitialLength(node);
    62          List<NameOccurrence> usage = node.getUsages();
    63          Map<Node, Map<Node, Integer>> blocks = new HashMap<Node, Map<Node, Integer>>();
    64          for (int ix = 0; ix < usage.size(); ix++) {
    65              NameOccurrence no = usage.get(ix);
    66              Node n = no.getLocation();
    67              if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
    68
    69                  if (!no.isOnLeftHandSide() && !InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "setLength")) {
    70                      continue;
    71                  }
    72                  if (constructorLength != -1 && anticipatedLength > constructorLength) {
    73                      anticipatedLength += processBlocks(blocks);
    74                      String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
    75                      addViolation(data, rootNode, param);
    76                  }
    77                  constructorLength = getConstructorLength(n, constructorLength);
    78                  rootNode = n;
    79                  anticipatedLength = getInitialLength(node);
    80              }
    81              ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
    82              int numChildren = s.jjtGetNumChildren();
    83              for (int jx = 0; jx < numChildren; jx++) {
    84              	Node sn = s.jjtGetChild(jx);
    85                  if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
    86                      continue;
    87                  }
    88                  int thisSize = 0;
    89                  Node block = getFirstParentBlock(sn);
    90                  if (isAdditive(sn)) {
    91                      thisSize = processAdditive(sn);
    92                  } else {
    93                      thisSize = processNode(sn);
    94                  }
    95                  if (block != null) {
    96                      storeBlockStatistics(blocks, thisSize, block);
    97                  } else {
    98                      anticipatedLength += thisSize;
    99                  }
    100             }
    101         }
    102         anticipatedLength += processBlocks(blocks);
    103         if (constructorLength != -1 && anticipatedLength > constructorLength) {
    104             String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
    105             addViolation(data, rootNode, param);
    106         }
    107         return data;
    108     }
    109
    110     /**
    111      * This rule is concerned with IF and Switch blocks. Process the block into
    112      * a local Map, from which we can later determine which is the longest block
    113      * inside
    114      *
    115      * @param blocks
    116      *            The map of blocks in the method being investigated
    117      * @param thisSize
    118      *            The size of the current block
    119      * @param block
    120      *            The block in question
    121      */
    122     private void storeBlockStatistics(Map<Node, Map<Node, Integer>> blocks, int thisSize, Node block) {
    123         Node statement = block.jjtGetParent();
    124         if (block.jjtGetParent() instanceof ASTIfStatement) {
    125             // Else Ifs are their own subnode in AST. So we have to
    126             // look a little farther up the tree to find the IF statement
    127             Node possibleStatement = statement.getFirstParentOfType(ASTIfStatement.class);
    128             while (possibleStatement instanceof ASTIfStatement) {
    129                 statement = possibleStatement;
    130                 possibleStatement = possibleStatement.getFirstParentOfType(ASTIfStatement.class);
    131             }
    132         }
    133         Map<Node, Integer> thisBranch = blocks.get(statement);
    134         if (thisBranch == null) {
    135             thisBranch = new HashMap<Node, Integer>();
    136             blocks.put(statement, thisBranch);
    137         }
    138         Integer x = thisBranch.get(block);
    139         if (x != null) {
    140             thisSize += x;
    141         }
    142         thisBranch.put(statement, thisSize);
    143     }
    144
    145     private int processBlocks(Map<Node, Map<Node, Integer>> blocks) {
    146         int anticipatedLength = 0;
    147         int ifLength = 0;
    148         for (Map.Entry<Node, Map<Node, Integer>> entry: blocks.entrySet()) {
    149             ifLength = 0;
    150             for (Map.Entry<Node, Integer> entry2: entry.getValue().entrySet()) {
    151                 Integer value = entry2.getValue();
    152                 ifLength = Math.max(ifLength, value.intValue());
    153             }
    154             anticipatedLength += ifLength;
    155         }
    156         return anticipatedLength;
    157     }
    158
    159     private int processAdditive(Node sn) {
    160         ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
    161         if (additive == null) {
    162             return 0;
    163         }
    164         int anticipatedLength = 0;
    165         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
    166             Node childNode = additive.jjtGetChild(ix);
    167             ASTLiteral literal = childNode.getFirstDescendantOfType(ASTLiteral.class);
    168             if (literal != null && literal.getImage() != null) {
    169                 anticipatedLength += literal.getImage().length() - 2;
    170             }
    171         }
    172
    173         return anticipatedLength;
    174     }
    175
    176     private static final boolean isStringOrCharLiteral(ASTLiteral literal) {
    177     	return literal.isStringLiteral() || literal.isCharLiteral();
    178     }
    179
    180     private int processNode(Node sn) {
    181         int anticipatedLength = 0;
    182         if ( sn != null ) {
    183             ASTPrimaryPrefix xn = sn.getFirstDescendantOfType(ASTPrimaryPrefix.class);
    184             if ( xn != null ) {
    185                 if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0) instanceof ASTLiteral) {
    186                 	ASTLiteral literal = (ASTLiteral) xn.jjtGetChild(0);
    187                     String str = xn.jjtGetChild(0).getImage();
    188                     if (str != null) {
    189 	                    if(isStringOrCharLiteral(literal)){
    190 	                        anticipatedLength += str.length() - 2;
    191 	                    } else if(literal.isIntLiteral() && str.startsWith("0x")){
    192 	                    	// but only if we are not inside a cast expression
    193 	                    	Node parentNode = literal.jjtGetParent().jjtGetParent().jjtGetParent();
    194 							if (parentNode instanceof ASTCastExpression
    195 	                    		&& parentNode.getFirstChildOfType(ASTType.class).getType() == char.class) {
    196 	                    		anticipatedLength += 1;
    197 	                    	} else {
    198     	                    	// e.g. 0xdeadbeef -> will be converted to a base 10 integer string: 3735928559
    199     	                    	anticipatedLength += String.valueOf(Long.parseLong(str.substring(2), 16)).length();
    200 	                    	}
    201 	                    } else {
    202 	                        anticipatedLength += str.length();
    203 	                    }
    204                     }
    205                 }
    206             }
    207         }
    208         StringBuffer sb = new StringBuffer();
    209         sb.append(3);
    210         return anticipatedLength;
    211     }
    212
    213     private int getConstructorLength(Node node, int constructorLength) {
    214         int iConstructorLength = constructorLength;
    215         Node block = node.getFirstParentOfType(ASTBlockStatement.class);
    216
    217         if (block == null) {
    218             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
    219         }
    220         if (block == null) {
    221             block = node.getFirstParentOfType(ASTFormalParameter.class);
    222             if (block != null) {
    223                 iConstructorLength = -1;
    224             } else {
    225             	return DEFAULT_BUFFER_SIZE;
    226             }
    227         }
    228
    229         //if there is any addition/subtraction going on then just use the default.
    230         ASTAdditiveExpression exp = block.getFirstDescendantOfType(ASTAdditiveExpression.class);
    231         if (exp != null){
    232             return DEFAULT_BUFFER_SIZE;
    233         }
    234         ASTMultiplicativeExpression mult = block.getFirstDescendantOfType(ASTMultiplicativeExpression.class);
    235         if (mult != null){
    236             return DEFAULT_BUFFER_SIZE;
    237         }
    238
    239         List<ASTLiteral> literals = block.findDescendantsOfType(ASTLiteral.class);
    240         if (literals.isEmpty()) {
    241             List<ASTName> name = block.findDescendantsOfType(ASTName.class);
    242             if (!name.isEmpty()) {
    243                 iConstructorLength = -1;
    244             }
    245         } else if (literals.size() == 1) {
    246         	ASTLiteral literal = literals.get(0);
    247             String str = literal.getImage();
    248             if (str == null) {
    249                 iConstructorLength = 0;
    250             } else if (isStringOrCharLiteral(literal)) {
    251                 // since it's not taken into account
    252                 // anywhere. only count the extra 16
    253                 // characters
    254                 iConstructorLength = 14 + str.length(); // don't add the constructor's length,
    255             } else if (literal.isIntLiteral() && str.startsWith("0x")) {
    256             	// bug 3516101 - the string could be a hex number
    257             	iConstructorLength = Integer.parseInt(str.substring(2), 16);
    258             } else {
    259                 iConstructorLength = Integer.parseInt(str);
    260             }
    261         } else {
    262             iConstructorLength = -1;
    263         }
    264
    265         if (iConstructorLength == 0) {
    266             if (constructorLength == -1) {
    267         	iConstructorLength = DEFAULT_BUFFER_SIZE;
    268             } else {
    269         	iConstructorLength = constructorLength;
    270             }
    271         }
    272
    273         return iConstructorLength;
    274     }
    275
    276
    277     private int getInitialLength(Node node) {
    278
    279     	Node block = node.getFirstParentOfType(ASTBlockStatement.class);
    280
    281         if (block == null) {
    282             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
    283             if (block == null) {
    284                 block = node.getFirstParentOfType(ASTFormalParameter.class);
    285             }
    286         }
    287         List<ASTLiteral> literals = block.findDescendantsOfType(ASTLiteral.class);
    288         if (literals.size() == 1) {
    289         	ASTLiteral literal = literals.get(0);
    290             String str = literal.getImage();
    291             if (str != null && isStringOrCharLiteral(literal)) {
    292                 return str.length() - 2; // take off the quotes
    293             }
    294         }
    295
    296         return 0;
    297     }
    298
    299     private boolean isAdditive(Node n) {
    300         return n.hasDescendantOfType(ASTAdditiveExpression.class);
    301     }
    302
    303     /**
    304      * Locate the block that the given node is in, if any
    305      *
    306      * @param node
    307      *            The node we're looking for a parent of
    308      * @return Node - The node that corresponds to any block that may be a
    309      *         parent of this object
    310      */
    311     private Node getFirstParentBlock(Node node) {
    312         Node parentNode = node.jjtGetParent();
    313
    314         Node lastNode = node;
    315         while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
    316             lastNode = parentNode;
    317             parentNode = parentNode.jjtGetParent();
    318         }
    319         if (parentNode instanceof ASTIfStatement) {
    320             parentNode = lastNode;
    321         } else if (parentNode instanceof ASTSwitchStatement) {
    322             parentNode = getSwitchParent(parentNode, lastNode);
    323         }
    324         return parentNode;
    325     }
    326
    327     /**
    328      * Determine which SwitchLabel we belong to inside a switch
    329      *
    330      * @param parentNode
    331      *            The parent node we're looking at
    332      * @param lastNode
    333      *            The last node processed
    334      * @return The parent node for the switch statement
    335      */
    336     private static Node getSwitchParent(Node parentNode, Node lastNode) {
    337         int allChildren = parentNode.jjtGetNumChildren();
    338         ASTSwitchLabel label = null;
    339         for (int ix = 0; ix < allChildren; ix++) {
    340             Node n = parentNode.jjtGetChild(ix);
    341             if (n instanceof ASTSwitchLabel) {
    342                 label = (ASTSwitchLabel) n;
    343             } else if (n.equals(lastNode)) {
    344                 parentNode = label;
    345                 break;
    346             }
    347         }
    348         return parentNode;
    349     }
    350
    351 }