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