001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.HashSet; 024import java.util.Set; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 031 032/** 033 * <p> 034 * Checks that any combination of String literals 035 * is on the left side of an {@code equals()} comparison. 036 * Also checks for String literals assigned to some field 037 * (such as {@code someString.equals(anotherString = "text")}). 038 * </p> 039 * <p>Rationale: Calling the {@code equals()} method on String literals 040 * will avoid a potential {@code NullPointerException}. Also, it is 041 * pretty common to see null checks right before equals comparisons, 042 * which is not necessary in the example below. 043 * </p> 044 * <p> 045 * For example, this code: 046 * </p> 047 * <pre> 048 * String nullString = null; 049 * nullString.equals("My_Sweet_String"); 050 * </pre> 051 * <p> 052 * should be refactored to: 053 * </p> 054 * <pre> 055 * String nullString = null; 056 * "My_Sweet_String".equals(nullString); 057 * </pre> 058 * <ul> 059 * <li> 060 * Property {@code ignoreEqualsIgnoreCase} - Control whether to ignore 061 * {@code String.equalsIgnoreCase(String)} invocations. 062 * Type is {@code boolean}. 063 * Default value is {@code false}. 064 * </li> 065 * </ul> 066 * <p> 067 * To configure the check: 068 * </p> 069 * <pre> 070 * <module name="EqualsAvoidNull"/> 071 * </pre> 072 * <p> 073 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 074 * </p> 075 * <p> 076 * Violation Message Keys: 077 * </p> 078 * <ul> 079 * <li> 080 * {@code equals.avoid.null} 081 * </li> 082 * <li> 083 * {@code equalsIgnoreCase.avoid.null} 084 * </li> 085 * </ul> 086 * 087 * @since 5.0 088 */ 089@FileStatefulCheck 090public class EqualsAvoidNullCheck extends AbstractCheck { 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null"; 097 098 /** 099 * A key is pointing to the warning message text in "messages.properties" 100 * file. 101 */ 102 public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null"; 103 104 /** Method name for comparison. */ 105 private static final String EQUALS = "equals"; 106 107 /** Type name for comparison. */ 108 private static final String STRING = "String"; 109 110 /** Curly for comparison. */ 111 private static final String LEFT_CURLY = "{"; 112 113 /** Control whether to ignore {@code String.equalsIgnoreCase(String)} invocations. */ 114 private boolean ignoreEqualsIgnoreCase; 115 116 /** Stack of sets of field names, one for each class of a set of nested classes. */ 117 private FieldFrame currentFrame; 118 119 @Override 120 public int[] getDefaultTokens() { 121 return getRequiredTokens(); 122 } 123 124 @Override 125 public int[] getAcceptableTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public int[] getRequiredTokens() { 131 return new int[] { 132 TokenTypes.METHOD_CALL, 133 TokenTypes.CLASS_DEF, 134 TokenTypes.METHOD_DEF, 135 TokenTypes.LITERAL_FOR, 136 TokenTypes.LITERAL_CATCH, 137 TokenTypes.LITERAL_TRY, 138 TokenTypes.LITERAL_SWITCH, 139 TokenTypes.VARIABLE_DEF, 140 TokenTypes.PARAMETER_DEF, 141 TokenTypes.CTOR_DEF, 142 TokenTypes.SLIST, 143 TokenTypes.OBJBLOCK, 144 TokenTypes.ENUM_DEF, 145 TokenTypes.ENUM_CONSTANT_DEF, 146 TokenTypes.LITERAL_NEW, 147 TokenTypes.LAMBDA, 148 }; 149 } 150 151 /** 152 * Setter to control whether to ignore {@code String.equalsIgnoreCase(String)} invocations. 153 * 154 * @param newValue whether to ignore checking 155 * {@code String.equalsIgnoreCase(String)}. 156 */ 157 public void setIgnoreEqualsIgnoreCase(boolean newValue) { 158 ignoreEqualsIgnoreCase = newValue; 159 } 160 161 @Override 162 public void beginTree(DetailAST rootAST) { 163 currentFrame = new FieldFrame(null); 164 } 165 166 @Override 167 public void visitToken(final DetailAST ast) { 168 switch (ast.getType()) { 169 case TokenTypes.VARIABLE_DEF: 170 case TokenTypes.PARAMETER_DEF: 171 currentFrame.addField(ast); 172 break; 173 case TokenTypes.METHOD_CALL: 174 processMethodCall(ast); 175 break; 176 case TokenTypes.SLIST: 177 processSlist(ast); 178 break; 179 case TokenTypes.LITERAL_NEW: 180 processLiteralNew(ast); 181 break; 182 case TokenTypes.OBJBLOCK: 183 final int parentType = ast.getParent().getType(); 184 if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) { 185 processFrame(ast); 186 } 187 break; 188 default: 189 processFrame(ast); 190 } 191 } 192 193 @Override 194 public void leaveToken(DetailAST ast) { 195 final int astType = ast.getType(); 196 if (astType == TokenTypes.SLIST) { 197 leaveSlist(ast); 198 } 199 else if (astType == TokenTypes.LITERAL_NEW) { 200 leaveLiteralNew(ast); 201 } 202 else if (astType == TokenTypes.OBJBLOCK) { 203 final int parentType = ast.getParent().getType(); 204 if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) { 205 currentFrame = currentFrame.getParent(); 206 } 207 } 208 else if (astType != TokenTypes.VARIABLE_DEF 209 && astType != TokenTypes.PARAMETER_DEF 210 && astType != TokenTypes.METHOD_CALL) { 211 currentFrame = currentFrame.getParent(); 212 } 213 } 214 215 @Override 216 public void finishTree(DetailAST ast) { 217 traverseFieldFrameTree(currentFrame); 218 } 219 220 /** 221 * Determine whether SLIST begins a block, determined by braces, and add it as 222 * a frame in this case. 223 * 224 * @param ast SLIST ast. 225 */ 226 private void processSlist(DetailAST ast) { 227 if (LEFT_CURLY.equals(ast.getText())) { 228 final FieldFrame frame = new FieldFrame(currentFrame); 229 currentFrame.addChild(frame); 230 currentFrame = frame; 231 } 232 } 233 234 /** 235 * Determine whether SLIST begins a block, determined by braces. 236 * 237 * @param ast SLIST ast. 238 */ 239 private void leaveSlist(DetailAST ast) { 240 if (LEFT_CURLY.equals(ast.getText())) { 241 currentFrame = currentFrame.getParent(); 242 } 243 } 244 245 /** 246 * Process CLASS_DEF, METHOD_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, 247 * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF, ENUM_CONSTANT_DEF. 248 * 249 * @param ast processed ast. 250 */ 251 private void processFrame(DetailAST ast) { 252 final FieldFrame frame = new FieldFrame(currentFrame); 253 final int astType = ast.getType(); 254 if (astType == TokenTypes.CLASS_DEF 255 || astType == TokenTypes.ENUM_DEF) { 256 frame.setClassOrEnumOrEnumConstDef(true); 257 frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText()); 258 } 259 currentFrame.addChild(frame); 260 currentFrame = frame; 261 } 262 263 /** 264 * Add the method call to the current frame if it should be processed. 265 * 266 * @param methodCall METHOD_CALL ast. 267 */ 268 private void processMethodCall(DetailAST methodCall) { 269 final DetailAST dot = methodCall.getFirstChild(); 270 if (dot.getType() == TokenTypes.DOT) { 271 final String methodName = dot.getLastChild().getText(); 272 if (EQUALS.equals(methodName) 273 || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) { 274 currentFrame.addMethodCall(methodCall); 275 } 276 } 277 } 278 279 /** 280 * Determine whether LITERAL_NEW is an anonymous class definition and add it as 281 * a frame in this case. 282 * 283 * @param ast LITERAL_NEW ast. 284 */ 285 private void processLiteralNew(DetailAST ast) { 286 if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { 287 final FieldFrame frame = new FieldFrame(currentFrame); 288 currentFrame.addChild(frame); 289 currentFrame = frame; 290 } 291 } 292 293 /** 294 * Determine whether LITERAL_NEW is an anonymous class definition and leave 295 * the frame it is in. 296 * 297 * @param ast LITERAL_NEW ast. 298 */ 299 private void leaveLiteralNew(DetailAST ast) { 300 if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { 301 currentFrame = currentFrame.getParent(); 302 } 303 } 304 305 /** 306 * Traverse the tree of the field frames to check all equals method calls. 307 * 308 * @param frame to check method calls in. 309 */ 310 private void traverseFieldFrameTree(FieldFrame frame) { 311 for (FieldFrame child: frame.getChildren()) { 312 traverseFieldFrameTree(child); 313 314 currentFrame = child; 315 child.getMethodCalls().forEach(this::checkMethodCall); 316 } 317 } 318 319 /** 320 * Check whether the method call should be violated. 321 * 322 * @param methodCall method call to check. 323 */ 324 private void checkMethodCall(DetailAST methodCall) { 325 DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild(); 326 if (objCalledOn.getType() == TokenTypes.DOT) { 327 objCalledOn = objCalledOn.getLastChild(); 328 } 329 final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild(); 330 if (containsOneArgument(methodCall) 331 && containsAllSafeTokens(expr) 332 && isCalledOnStringFieldOrVariable(objCalledOn)) { 333 final String methodName = methodCall.getFirstChild().getLastChild().getText(); 334 if (EQUALS.equals(methodName)) { 335 log(methodCall, MSG_EQUALS_AVOID_NULL); 336 } 337 else { 338 log(methodCall, MSG_EQUALS_IGNORE_CASE_AVOID_NULL); 339 } 340 } 341 } 342 343 /** 344 * Verify that method call has one argument. 345 * 346 * @param methodCall METHOD_CALL DetailAST 347 * @return true if method call has one argument. 348 */ 349 private static boolean containsOneArgument(DetailAST methodCall) { 350 final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST); 351 return elist.getChildCount() == 1; 352 } 353 354 /** 355 * Looks for all "safe" Token combinations in the argument 356 * expression branch. 357 * 358 * @param expr the argument expression 359 * @return - true if any child matches the set of tokens, false if not 360 */ 361 private static boolean containsAllSafeTokens(final DetailAST expr) { 362 DetailAST arg = expr.getFirstChild(); 363 arg = skipVariableAssign(arg); 364 365 boolean argIsNotNull = false; 366 if (arg.getType() == TokenTypes.PLUS) { 367 DetailAST child = arg.getFirstChild(); 368 while (child != null 369 && !argIsNotNull) { 370 argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL 371 || child.getType() == TokenTypes.IDENT; 372 child = child.getNextSibling(); 373 } 374 } 375 else { 376 argIsNotNull = arg.getType() == TokenTypes.STRING_LITERAL; 377 } 378 379 return argIsNotNull; 380 } 381 382 /** 383 * Skips over an inner assign portion of an argument expression. 384 * 385 * @param currentAST current token in the argument expression 386 * @return the next relevant token 387 */ 388 private static DetailAST skipVariableAssign(final DetailAST currentAST) { 389 DetailAST result = currentAST; 390 while (result.getType() == TokenTypes.LPAREN) { 391 result = result.getNextSibling(); 392 } 393 if (result.getType() == TokenTypes.ASSIGN) { 394 result = result.getFirstChild().getNextSibling(); 395 } 396 return result; 397 } 398 399 /** 400 * Determine, whether equals method is called on a field of String type. 401 * 402 * @param objCalledOn object ast. 403 * @return true if the object is of String type. 404 */ 405 private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) { 406 final boolean result; 407 final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling(); 408 if (previousSiblingAst == null) { 409 result = isStringFieldOrVariable(objCalledOn); 410 } 411 else { 412 if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) { 413 result = isStringFieldOrVariableFromThisInstance(objCalledOn); 414 } 415 else { 416 final String className = previousSiblingAst.getText(); 417 result = isStringFieldOrVariableFromClass(objCalledOn, className); 418 } 419 } 420 return result; 421 } 422 423 /** 424 * Whether the field or the variable is of String type. 425 * 426 * @param objCalledOn the field or the variable to check. 427 * @return true if the field or the variable is of String type. 428 */ 429 private boolean isStringFieldOrVariable(DetailAST objCalledOn) { 430 boolean result = false; 431 final String name = objCalledOn.getText(); 432 FieldFrame frame = currentFrame; 433 while (frame != null) { 434 final DetailAST field = frame.findField(name); 435 if (field != null 436 && (frame.isClassOrEnumOrEnumConstDef() 437 || checkLineNo(field, objCalledOn))) { 438 result = STRING.equals(getFieldType(field)); 439 break; 440 } 441 frame = frame.getParent(); 442 } 443 return result; 444 } 445 446 /** 447 * Whether the field or the variable from THIS instance is of String type. 448 * 449 * @param objCalledOn the field or the variable from THIS instance to check. 450 * @return true if the field or the variable from THIS instance is of String type. 451 */ 452 private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) { 453 final String name = objCalledOn.getText(); 454 final DetailAST field = getObjectFrame(currentFrame).findField(name); 455 return STRING.equals(getFieldType(field)); 456 } 457 458 /** 459 * Whether the field or the variable from the specified class is of String type. 460 * 461 * @param objCalledOn the field or the variable from the specified class to check. 462 * @param className the name of the class to check in. 463 * @return true if the field or the variable from the specified class is of String type. 464 */ 465 private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn, 466 final String className) { 467 boolean result = false; 468 final String name = objCalledOn.getText(); 469 FieldFrame frame = getObjectFrame(currentFrame); 470 while (frame != null) { 471 if (className.equals(frame.getFrameName())) { 472 final DetailAST field = frame.findField(name); 473 result = STRING.equals(getFieldType(field)); 474 break; 475 } 476 frame = getObjectFrame(frame.getParent()); 477 } 478 return result; 479 } 480 481 /** 482 * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 483 * 484 * @param frame to start the search from. 485 * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 486 */ 487 private static FieldFrame getObjectFrame(FieldFrame frame) { 488 FieldFrame objectFrame = frame; 489 while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) { 490 objectFrame = objectFrame.getParent(); 491 } 492 return objectFrame; 493 } 494 495 /** 496 * Check whether the field is declared before the method call in case of 497 * methods and initialization blocks. 498 * 499 * @param field field to check. 500 * @param objCalledOn object equals method called on. 501 * @return true if the field is declared before the method call. 502 */ 503 private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) { 504 boolean result = false; 505 if (CheckUtil.isBeforeInSource(field, objCalledOn)) { 506 result = true; 507 } 508 return result; 509 } 510 511 /** 512 * Get field type. 513 * 514 * @param field to get the type from. 515 * @return type of the field. 516 */ 517 private static String getFieldType(DetailAST field) { 518 String fieldType = null; 519 final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE) 520 .findFirstToken(TokenTypes.IDENT); 521 if (identAst != null) { 522 fieldType = identAst.getText(); 523 } 524 return fieldType; 525 } 526 527 /** 528 * Holds the names of fields of a type. 529 */ 530 private static class FieldFrame { 531 532 /** Parent frame. */ 533 private final FieldFrame parent; 534 535 /** Set of frame's children. */ 536 private final Set<FieldFrame> children = new HashSet<>(); 537 538 /** Set of fields. */ 539 private final Set<DetailAST> fields = new HashSet<>(); 540 541 /** Set of equals calls. */ 542 private final Set<DetailAST> methodCalls = new HashSet<>(); 543 544 /** Name of the class, enum or enum constant declaration. */ 545 private String frameName; 546 547 /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */ 548 private boolean classOrEnumOrEnumConstDef; 549 550 /** 551 * Creates new frame. 552 * 553 * @param parent parent frame. 554 */ 555 /* package */ FieldFrame(FieldFrame parent) { 556 this.parent = parent; 557 } 558 559 /** 560 * Set the frame name. 561 * 562 * @param frameName value to set. 563 */ 564 public void setFrameName(String frameName) { 565 this.frameName = frameName; 566 } 567 568 /** 569 * Getter for the frame name. 570 * 571 * @return frame name. 572 */ 573 public String getFrameName() { 574 return frameName; 575 } 576 577 /** 578 * Getter for the parent frame. 579 * 580 * @return parent frame. 581 */ 582 public FieldFrame getParent() { 583 return parent; 584 } 585 586 /** 587 * Getter for frame's children. 588 * 589 * @return children of this frame. 590 */ 591 public Set<FieldFrame> getChildren() { 592 return Collections.unmodifiableSet(children); 593 } 594 595 /** 596 * Add child frame to this frame. 597 * 598 * @param child frame to add. 599 */ 600 public void addChild(FieldFrame child) { 601 children.add(child); 602 } 603 604 /** 605 * Add field to this FieldFrame. 606 * 607 * @param field the ast of the field. 608 */ 609 public void addField(DetailAST field) { 610 if (field.findFirstToken(TokenTypes.IDENT) != null) { 611 fields.add(field); 612 } 613 } 614 615 /** 616 * Sets isClassOrEnum. 617 * 618 * @param value value to set. 619 */ 620 public void setClassOrEnumOrEnumConstDef(boolean value) { 621 classOrEnumOrEnumConstDef = value; 622 } 623 624 /** 625 * Getter for classOrEnumOrEnumConstDef. 626 * 627 * @return classOrEnumOrEnumConstDef. 628 */ 629 public boolean isClassOrEnumOrEnumConstDef() { 630 return classOrEnumOrEnumConstDef; 631 } 632 633 /** 634 * Add method call to this frame. 635 * 636 * @param methodCall METHOD_CALL ast. 637 */ 638 public void addMethodCall(DetailAST methodCall) { 639 methodCalls.add(methodCall); 640 } 641 642 /** 643 * Determines whether this FieldFrame contains the field. 644 * 645 * @param name name of the field to check. 646 * @return true if this FieldFrame contains instance field field. 647 */ 648 public DetailAST findField(String name) { 649 DetailAST resultField = null; 650 for (DetailAST field: fields) { 651 if (getFieldName(field).equals(name)) { 652 resultField = field; 653 break; 654 } 655 } 656 return resultField; 657 } 658 659 /** 660 * Getter for frame's method calls. 661 * 662 * @return method calls of this frame. 663 */ 664 public Set<DetailAST> getMethodCalls() { 665 return Collections.unmodifiableSet(methodCalls); 666 } 667 668 /** 669 * Get the name of the field. 670 * 671 * @param field to get the name from. 672 * @return name of the field. 673 */ 674 private static String getFieldName(DetailAST field) { 675 return field.findFirstToken(TokenTypes.IDENT).getText(); 676 } 677 678 } 679 680}