001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.Collections; 023import java.util.Set; 024 025import com.google.common.collect.Sets; 026import com.puppycrawl.tools.checkstyle.api.Check; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * Checks that any combination of String literals 032 * is on the left side of an equals() comparison. 033 * Also checks for String literals assigned to some field 034 * (such as <code>someString.equals(anotherString = "text")</code>). 035 * 036 * <p>Rationale: Calling the equals() method on String literals 037 * will avoid a potential NullPointerException. Also, it is 038 * pretty common to see null check right before equals comparisons 039 * which is not necessary in the below example. 040 * 041 * <p>For example: 042 * 043 * <pre> 044 * {@code 045 * String nullString = null; 046 * nullString.equals("My_Sweet_String"); 047 * } 048 * </pre> 049 * should be refactored to 050 * 051 * <pre> 052 * {@code 053 * String nullString = null; 054 * "My_Sweet_String".equals(nullString); 055 * } 056 * </pre> 057 * 058 * @author Travis Schneeberger 059 * @author Vladislav Lisetskiy 060 */ 061public class EqualsAvoidNullCheck extends Check { 062 063 /** 064 * A key is pointing to the warning message text in "messages.properties" 065 * file. 066 */ 067 public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null"; 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" 071 * file. 072 */ 073 public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null"; 074 075 /** Method name for comparison. */ 076 private static final String EQUALS = "equals"; 077 078 /** Type name for comparison. */ 079 private static final String STRING = "String"; 080 081 /** Whether to process equalsIgnoreCase() invocations. */ 082 private boolean ignoreEqualsIgnoreCase; 083 084 /** Stack of sets of field names, one for each class of a set of nested classes. */ 085 private FieldFrame currentFrame; 086 087 @Override 088 public int[] getDefaultTokens() { 089 return getAcceptableTokens(); 090 } 091 092 @Override 093 public int[] getAcceptableTokens() { 094 return new int[] { 095 TokenTypes.METHOD_CALL, 096 TokenTypes.CLASS_DEF, 097 TokenTypes.METHOD_DEF, 098 TokenTypes.LITERAL_IF, 099 TokenTypes.LITERAL_FOR, 100 TokenTypes.LITERAL_WHILE, 101 TokenTypes.LITERAL_DO, 102 TokenTypes.LITERAL_CATCH, 103 TokenTypes.LITERAL_TRY, 104 TokenTypes.VARIABLE_DEF, 105 TokenTypes.PARAMETER_DEF, 106 TokenTypes.CTOR_DEF, 107 TokenTypes.SLIST, 108 TokenTypes.ENUM_DEF, 109 TokenTypes.ENUM_CONSTANT_DEF, 110 TokenTypes.LITERAL_NEW, 111 }; 112 } 113 114 @Override 115 public int[] getRequiredTokens() { 116 return getAcceptableTokens(); 117 } 118 119 /** 120 * Whether to ignore checking {@code String.equalsIgnoreCase(String)}. 121 * @param newValue whether to ignore checking 122 * {@code String.equalsIgnoreCase(String)}. 123 */ 124 public void setIgnoreEqualsIgnoreCase(boolean newValue) { 125 ignoreEqualsIgnoreCase = newValue; 126 } 127 128 @Override 129 public void beginTree(DetailAST rootAST) { 130 currentFrame = new FieldFrame(null); 131 } 132 133 @Override 134 public void visitToken(final DetailAST ast) { 135 switch (ast.getType()) { 136 case TokenTypes.VARIABLE_DEF: 137 case TokenTypes.PARAMETER_DEF: 138 currentFrame.addField(ast); 139 break; 140 case TokenTypes.METHOD_CALL: 141 processMethodCall(ast); 142 break; 143 case TokenTypes.SLIST: 144 processSlist(ast); 145 break; 146 case TokenTypes.LITERAL_NEW: 147 processLiteralNew(ast); 148 break; 149 default: 150 processFrame(ast); 151 } 152 } 153 154 @Override 155 public void leaveToken(DetailAST ast) { 156 final int astType = ast.getType(); 157 if (astType != TokenTypes.VARIABLE_DEF 158 && astType != TokenTypes.PARAMETER_DEF 159 && astType != TokenTypes.METHOD_CALL 160 && astType != TokenTypes.SLIST 161 && astType != TokenTypes.LITERAL_NEW 162 || astType == TokenTypes.LITERAL_NEW 163 && ast.branchContains(TokenTypes.LCURLY)) { 164 currentFrame = currentFrame.getParent(); 165 } 166 else if (astType == TokenTypes.SLIST) { 167 leaveSlist(ast); 168 } 169 } 170 171 @Override 172 public void finishTree(DetailAST ast) { 173 traverseFieldFrameTree(currentFrame); 174 } 175 176 /** 177 * Determine whether SLIST begins static or non-static block and add it as 178 * a frame in this case. 179 * @param ast SLIST ast. 180 */ 181 private void processSlist(DetailAST ast) { 182 final int parentType = ast.getParent().getType(); 183 if (parentType == TokenTypes.SLIST 184 || parentType == TokenTypes.STATIC_INIT 185 || parentType == TokenTypes.INSTANCE_INIT) { 186 final FieldFrame frame = new FieldFrame(currentFrame); 187 currentFrame.addChild(frame); 188 currentFrame = frame; 189 } 190 } 191 192 /** 193 * Determine whether SLIST begins static or non-static block and 194 * @param ast SLIST ast. 195 */ 196 private void leaveSlist(DetailAST ast) { 197 final int parentType = ast.getParent().getType(); 198 if (parentType == TokenTypes.SLIST 199 || parentType == TokenTypes.STATIC_INIT 200 || parentType == TokenTypes.INSTANCE_INIT) { 201 currentFrame = currentFrame.getParent(); 202 } 203 } 204 205 /** 206 * Process CLASS_DEF, METHOD_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, 207 * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF, ENUM_CONSTANT_DEF. 208 * @param ast processed ast. 209 */ 210 private void processFrame(DetailAST ast) { 211 final FieldFrame frame = new FieldFrame(currentFrame); 212 final int astType = ast.getType(); 213 if (astType == TokenTypes.CLASS_DEF 214 || astType == TokenTypes.ENUM_DEF 215 || astType == TokenTypes.ENUM_CONSTANT_DEF) { 216 frame.setClassOrEnumOrEnumConstDef(true); 217 frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText()); 218 } 219 currentFrame.addChild(frame); 220 currentFrame = frame; 221 } 222 223 /** 224 * Add the method call to the current frame if it should be processed. 225 * @param methodCall METHOD_CALL ast. 226 */ 227 private void processMethodCall(DetailAST methodCall) { 228 final DetailAST dot = methodCall.getFirstChild(); 229 if (dot.getType() == TokenTypes.DOT) { 230 final String methodName = dot.getLastChild().getText(); 231 if (EQUALS.equals(methodName) 232 || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) { 233 currentFrame.addMethodCall(methodCall); 234 } 235 } 236 } 237 238 /** 239 * Determine whether LITERAL_NEW is an anonymous class definition and add it as 240 * a frame in this case. 241 * @param ast LITERAL_NEW ast. 242 */ 243 private void processLiteralNew(DetailAST ast) { 244 if (ast.branchContains(TokenTypes.LCURLY)) { 245 final FieldFrame frame = new FieldFrame(currentFrame); 246 currentFrame.addChild(frame); 247 currentFrame = frame; 248 } 249 } 250 251 /** 252 * Traverse the tree of the field frames to check all equals method calls. 253 * @param frame to check method calls in. 254 */ 255 private void traverseFieldFrameTree(FieldFrame frame) { 256 for (FieldFrame child: frame.getChildren()) { 257 if (!child.getChildren().isEmpty()) { 258 traverseFieldFrameTree(child); 259 } 260 currentFrame = child; 261 for (DetailAST methodCall: child.getMethodCalls()) { 262 checkMethodCall(methodCall); 263 } 264 } 265 } 266 267 /** 268 * Check whether the method call should be violated. 269 * @param methodCall method call to check. 270 */ 271 private void checkMethodCall(DetailAST methodCall) { 272 DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild(); 273 if (objCalledOn.getType() == TokenTypes.DOT) { 274 objCalledOn = objCalledOn.getLastChild(); 275 } 276 final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild(); 277 if (isObjectValid(objCalledOn) 278 && containsOneArgument(methodCall) 279 && containsAllSafeTokens(expr) 280 && isCalledOnStringField(objCalledOn)) { 281 final String methodName = methodCall.getFirstChild().getLastChild().getText(); 282 if (EQUALS.equals(methodName)) { 283 log(methodCall.getLineNo(), methodCall.getColumnNo(), 284 MSG_EQUALS_AVOID_NULL); 285 } 286 else { 287 log(methodCall.getLineNo(), methodCall.getColumnNo(), 288 MSG_EQUALS_IGNORE_CASE_AVOID_NULL); 289 } 290 } 291 } 292 293 /** 294 * Check whether the object equals method is called on is not a String literal 295 * and not too complex. 296 * @param objCalledOn the object equals method is called on ast. 297 * @return true if the object is valid. 298 */ 299 private static boolean isObjectValid(DetailAST objCalledOn) { 300 boolean result = true; 301 final DetailAST previousSibling = objCalledOn.getPreviousSibling(); 302 if (previousSibling != null 303 && previousSibling.getType() == TokenTypes.DOT) { 304 result = false; 305 } 306 if (isStringLiteral(objCalledOn)) { 307 result = false; 308 } 309 return result; 310 } 311 312 /** 313 * Checks for calling equals on String literal and 314 * anon object which cannot be null. 315 * @param objCalledOn object AST 316 * @return if it is string literal 317 */ 318 private static boolean isStringLiteral(DetailAST objCalledOn) { 319 return objCalledOn.getType() == TokenTypes.STRING_LITERAL 320 || objCalledOn.getType() == TokenTypes.LITERAL_NEW; 321 } 322 323 /** 324 * Verify that method call has one argument. 325 * 326 * @param methodCall METHOD_CALL DetailAST 327 * @return true if method call has one argument. 328 */ 329 private static boolean containsOneArgument(DetailAST methodCall) { 330 final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST); 331 return elist.getChildCount() == 1; 332 } 333 334 /** 335 * Looks for all "safe" Token combinations in the argument 336 * expression branch. 337 * @param expr the argument expression 338 * @return - true if any child matches the set of tokens, false if not 339 */ 340 private static boolean containsAllSafeTokens(final DetailAST expr) { 341 DetailAST arg = expr.getFirstChild(); 342 if (arg.branchContains(TokenTypes.METHOD_CALL)) { 343 return false; 344 } 345 arg = skipVariableAssign(arg); 346 347 //Plus assignment can have ill affects 348 //do not want to recommend moving expression 349 //See example: 350 //String s = "SweetString"; 351 //s.equals(s += "SweetString"); //false 352 //s = "SweetString"; 353 //(s += "SweetString").equals(s); //true 354 355 return !arg.branchContains(TokenTypes.PLUS_ASSIGN) 356 && !arg.branchContains(TokenTypes.IDENT) 357 && !arg.branchContains(TokenTypes.LITERAL_NULL); 358 } 359 360 /** 361 * Skips over an inner assign portion of an argument expression. 362 * @param currentAST current token in the argument expression 363 * @return the next relevant token 364 */ 365 private static DetailAST skipVariableAssign(final DetailAST currentAST) { 366 if (currentAST.getType() == TokenTypes.ASSIGN 367 && currentAST.getFirstChild().getType() == TokenTypes.IDENT) { 368 return currentAST.getFirstChild().getNextSibling(); 369 } 370 return currentAST; 371 } 372 373 /** 374 * Determine, whether equals method is called on a field of String type. 375 * @param objCalledOn object ast. 376 * @return true if the object is of String type. 377 */ 378 private boolean isCalledOnStringField(DetailAST objCalledOn) { 379 boolean result = false; 380 final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling(); 381 final String name = objCalledOn.getText(); 382 if (previousSiblingAst != null) { 383 if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) { 384 final DetailAST field = getObjectFrame(currentFrame).findField(name); 385 result = STRING.equals(getFieldType(field)); 386 } 387 else { 388 final String className = previousSiblingAst.getText(); 389 FieldFrame frame = getObjectFrame(currentFrame); 390 while (frame != null) { 391 if (className.equals(frame.getFrameName())) { 392 final DetailAST field = frame.findField(name); 393 result = STRING.equals(getFieldType(field)); 394 break; 395 } 396 frame = getObjectFrame(frame.getParent()); 397 } 398 } 399 } 400 else { 401 FieldFrame frame = currentFrame; 402 while (frame != null) { 403 final DetailAST field = frame.findField(name); 404 if (field != null 405 && (frame.isClassOrEnumOrEnumConstDef() 406 || checkLineNo(field, objCalledOn))) { 407 result = STRING.equals(getFieldType(field)); 408 break; 409 } 410 frame = frame.getParent(); 411 } 412 } 413 return result; 414 } 415 416 /** 417 * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 418 * @param frame to start the search from. 419 * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 420 */ 421 private static FieldFrame getObjectFrame(FieldFrame frame) { 422 FieldFrame objectFrame = frame; 423 while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) { 424 objectFrame = objectFrame.getParent(); 425 } 426 return objectFrame; 427 } 428 429 /** 430 * Check whether the field is declared before the method call in case of 431 * methods and initialization blocks. 432 * @param field field to check. 433 * @param objCalledOn object equals method called on. 434 * @return true if the field is declared before the method call. 435 */ 436 private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) { 437 boolean result = false; 438 if (field.getLineNo() < objCalledOn.getLineNo() 439 || field.getLineNo() == objCalledOn.getLineNo() 440 && field.getColumnNo() < objCalledOn.getColumnNo()) { 441 result = true; 442 } 443 return result; 444 } 445 446 /** 447 * Get field type. 448 * @param field to get the type from. 449 * @return type of the field. 450 */ 451 private static String getFieldType(DetailAST field) { 452 String fieldType = null; 453 final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE) 454 .findFirstToken(TokenTypes.IDENT); 455 if (identAst != null) { 456 fieldType = identAst.getText(); 457 } 458 return fieldType; 459 } 460 461 /** 462 * Holds the names of fields of a type. 463 */ 464 private static class FieldFrame { 465 /** Name of the class, enum or enum constant declaration. */ 466 private String frameName; 467 468 /** Parent frame. */ 469 private final FieldFrame parent; 470 471 /** Set of frame's children. */ 472 private final Set<FieldFrame> children = Sets.newHashSet(); 473 474 /** Set of fields. */ 475 private final Set<DetailAST> fields = Sets.newHashSet(); 476 477 /** Set of equals calls. */ 478 private final Set<DetailAST> methodCalls = Sets.newHashSet(); 479 480 /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */ 481 private boolean classOrEnumOrEnumConstDef; 482 483 /** 484 * Creates new frame. 485 * @param parent parent frame. 486 */ 487 FieldFrame(FieldFrame parent) { 488 this.parent = parent; 489 } 490 491 /** 492 * Set the frame name. 493 * @param frameName value to set. 494 */ 495 public void setFrameName(String frameName) { 496 this.frameName = frameName; 497 } 498 499 /** 500 * Getter for the frame name. 501 * @return frame name. 502 */ 503 public String getFrameName() { 504 return frameName; 505 } 506 507 /** 508 * Getter for the parent frame. 509 * @return parent frame. 510 */ 511 public FieldFrame getParent() { 512 return parent; 513 } 514 515 /** 516 * Getter for frame's children. 517 * @return children of this frame. 518 */ 519 public Set<FieldFrame> getChildren() { 520 return Collections.unmodifiableSet(children); 521 } 522 523 /** 524 * Add child frame to this frame. 525 * @param child frame to add. 526 */ 527 public void addChild(FieldFrame child) { 528 children.add(child); 529 } 530 531 /** 532 * Add field to this FieldFrame. 533 * @param field the ast of the field. 534 */ 535 public void addField(DetailAST field) { 536 fields.add(field); 537 } 538 539 /** 540 * Sets isClassOrEnum. 541 * @param value value to set. 542 */ 543 public void setClassOrEnumOrEnumConstDef(boolean value) { 544 classOrEnumOrEnumConstDef = value; 545 } 546 547 /** 548 * Getter for classOrEnumOrEnumConstDef. 549 * @return classOrEnumOrEnumConstDef. 550 */ 551 public boolean isClassOrEnumOrEnumConstDef() { 552 return classOrEnumOrEnumConstDef; 553 } 554 555 /** 556 * Add method call to this frame. 557 * @param methodCall METHOD_CALL ast. 558 */ 559 public void addMethodCall(DetailAST methodCall) { 560 methodCalls.add(methodCall); 561 } 562 563 /** 564 * Determines whether this FieldFrame contains the field. 565 * @param name name of the field to check. 566 * @return true if this FieldFrame contains instance field field. 567 */ 568 public DetailAST findField(String name) { 569 for (DetailAST field: fields) { 570 if (getFieldName(field).equals(name)) { 571 return field; 572 } 573 } 574 return null; 575 } 576 577 /** 578 * Getter for frame's method calls. 579 * @return method calls of this frame. 580 */ 581 public Set<DetailAST> getMethodCalls() { 582 return Collections.unmodifiableSet(methodCalls); 583 } 584 585 /** 586 * Get the name of the field. 587 * @param field to get the name from. 588 * @return name of the field. 589 */ 590 private static String getFieldName(DetailAST field) { 591 return field.findFirstToken(TokenTypes.IDENT).getText(); 592 } 593 } 594}