View Javadoc

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