001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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; 030 031/** 032 * Checks that any combination of String literals 033 * is on the left side of an equals() comparison. 034 * Also checks for String literals assigned to some field 035 * (such as {@code someString.equals(anotherString = "text")}). 036 * 037 * <p>Rationale: Calling the equals() method on String literals 038 * will avoid a potential NullPointerException. Also, it is 039 * pretty common to see null check right before equals comparisons 040 * which is not necessary in the below example. 041 * 042 * <p>For example: 043 * 044 * <pre> 045 * {@code 046 * String nullString = null; 047 * nullString.equals("My_Sweet_String"); 048 * } 049 * </pre> 050 * should be refactored to 051 * 052 * <pre> 053 * {@code 054 * String nullString = null; 055 * "My_Sweet_String".equals(nullString); 056 * } 057 * </pre> 058 * 059 */ 060@FileStatefulCheck 061public class EqualsAvoidNullCheck extends AbstractCheck { 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 getRequiredTokens(); 090 } 091 092 @Override 093 public int[] getAcceptableTokens() { 094 return getRequiredTokens(); 095 } 096 097 @Override 098 public int[] getRequiredTokens() { 099 return new int[] { 100 TokenTypes.METHOD_CALL, 101 TokenTypes.CLASS_DEF, 102 TokenTypes.METHOD_DEF, 103 TokenTypes.LITERAL_IF, 104 TokenTypes.LITERAL_FOR, 105 TokenTypes.LITERAL_WHILE, 106 TokenTypes.LITERAL_DO, 107 TokenTypes.LITERAL_CATCH, 108 TokenTypes.LITERAL_TRY, 109 TokenTypes.VARIABLE_DEF, 110 TokenTypes.PARAMETER_DEF, 111 TokenTypes.CTOR_DEF, 112 TokenTypes.SLIST, 113 TokenTypes.ENUM_DEF, 114 TokenTypes.ENUM_CONSTANT_DEF, 115 TokenTypes.LITERAL_NEW, 116 }; 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.findFirstToken(TokenTypes.OBJBLOCK) != null) { 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. 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.findFirstToken(TokenTypes.OBJBLOCK) != null) { 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 child.getMethodCalls().forEach(this::checkMethodCall); 262 } 263 } 264 265 /** 266 * Check whether the method call should be violated. 267 * @param methodCall method call to check. 268 */ 269 private void checkMethodCall(DetailAST methodCall) { 270 DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild(); 271 if (objCalledOn.getType() == TokenTypes.DOT) { 272 objCalledOn = objCalledOn.getLastChild(); 273 } 274 final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild(); 275 if (isObjectValid(objCalledOn) 276 && containsOneArgument(methodCall) 277 && containsAllSafeTokens(expr) 278 && isCalledOnStringFieldOrVariable(objCalledOn)) { 279 final String methodName = methodCall.getFirstChild().getLastChild().getText(); 280 if (EQUALS.equals(methodName)) { 281 log(methodCall.getLineNo(), methodCall.getColumnNo(), 282 MSG_EQUALS_AVOID_NULL); 283 } 284 else { 285 log(methodCall.getLineNo(), methodCall.getColumnNo(), 286 MSG_EQUALS_IGNORE_CASE_AVOID_NULL); 287 } 288 } 289 } 290 291 /** 292 * Check whether the object equals method is called on is not a String literal 293 * and not too complex. 294 * @param objCalledOn the object equals method is called on ast. 295 * @return true if the object is valid. 296 */ 297 private static boolean isObjectValid(DetailAST objCalledOn) { 298 boolean result = true; 299 final DetailAST previousSibling = objCalledOn.getPreviousSibling(); 300 if (previousSibling != null 301 && previousSibling.getType() == TokenTypes.DOT) { 302 result = false; 303 } 304 if (isStringLiteral(objCalledOn)) { 305 result = false; 306 } 307 return result; 308 } 309 310 /** 311 * Checks for calling equals on String literal and 312 * anon object which cannot be null. 313 * @param objCalledOn object AST 314 * @return if it is string literal 315 */ 316 private static boolean isStringLiteral(DetailAST objCalledOn) { 317 return objCalledOn.getType() == TokenTypes.STRING_LITERAL 318 || objCalledOn.getType() == TokenTypes.LITERAL_NEW; 319 } 320 321 /** 322 * Verify that method call has one argument. 323 * 324 * @param methodCall METHOD_CALL DetailAST 325 * @return true if method call has one argument. 326 */ 327 private static boolean containsOneArgument(DetailAST methodCall) { 328 final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST); 329 return elist.getChildCount() == 1; 330 } 331 332 /** 333 * Looks for all "safe" Token combinations in the argument 334 * expression branch. 335 * @param expr the argument expression 336 * @return - true if any child matches the set of tokens, false if not 337 */ 338 private static boolean containsAllSafeTokens(final DetailAST expr) { 339 DetailAST arg = expr.getFirstChild(); 340 arg = skipVariableAssign(arg); 341 342 boolean argIsNotNull = false; 343 if (arg.getType() == TokenTypes.PLUS) { 344 DetailAST child = arg.getFirstChild(); 345 while (child != null 346 && !argIsNotNull) { 347 argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL 348 || child.getType() == TokenTypes.IDENT; 349 child = child.getNextSibling(); 350 } 351 } 352 353 return argIsNotNull 354 || !arg.branchContains(TokenTypes.IDENT) 355 && !arg.branchContains(TokenTypes.LITERAL_NULL); 356 } 357 358 /** 359 * Skips over an inner assign portion of an argument expression. 360 * @param currentAST current token in the argument expression 361 * @return the next relevant token 362 */ 363 private static DetailAST skipVariableAssign(final DetailAST currentAST) { 364 DetailAST result = currentAST; 365 if (currentAST.getType() == TokenTypes.ASSIGN 366 && currentAST.getFirstChild().getType() == TokenTypes.IDENT) { 367 result = currentAST.getFirstChild().getNextSibling(); 368 } 369 return result; 370 } 371 372 /** 373 * Determine, whether equals method is called on a field of String type. 374 * @param objCalledOn object ast. 375 * @return true if the object is of String type. 376 */ 377 private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) { 378 final boolean result; 379 final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling(); 380 if (previousSiblingAst == null) { 381 result = isStringFieldOrVariable(objCalledOn); 382 } 383 else { 384 if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) { 385 result = isStringFieldOrVariableFromThisInstance(objCalledOn); 386 } 387 else { 388 final String className = previousSiblingAst.getText(); 389 result = isStringFieldOrVariableFromClass(objCalledOn, className); 390 } 391 } 392 return result; 393 } 394 395 /** 396 * Whether the field or the variable is of String type. 397 * @param objCalledOn the field or the variable to check. 398 * @return true if the field or the variable is of String type. 399 */ 400 private boolean isStringFieldOrVariable(DetailAST objCalledOn) { 401 boolean result = false; 402 final String name = objCalledOn.getText(); 403 FieldFrame frame = currentFrame; 404 while (frame != null) { 405 final DetailAST field = frame.findField(name); 406 if (field != null 407 && (frame.isClassOrEnumOrEnumConstDef() 408 || checkLineNo(field, objCalledOn))) { 409 result = STRING.equals(getFieldType(field)); 410 break; 411 } 412 frame = frame.getParent(); 413 } 414 return result; 415 } 416 417 /** 418 * Whether the field or the variable from THIS instance is of String type. 419 * @param objCalledOn the field or the variable from THIS instance to check. 420 * @return true if the field or the variable from THIS instance is of String type. 421 */ 422 private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) { 423 boolean result = false; 424 final String name = objCalledOn.getText(); 425 final DetailAST field = getObjectFrame(currentFrame).findField(name); 426 if (field != null) { 427 result = STRING.equals(getFieldType(field)); 428 } 429 return result; 430 } 431 432 /** 433 * Whether the field or the variable from the specified class is of String type. 434 * @param objCalledOn the field or the variable from the specified class to check. 435 * @param className the name of the class to check in. 436 * @return true if the field or the variable from the specified class is of String type. 437 */ 438 private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn, 439 final String className) { 440 boolean result = false; 441 final String name = objCalledOn.getText(); 442 FieldFrame frame = getObjectFrame(currentFrame); 443 while (frame != null) { 444 if (className.equals(frame.getFrameName())) { 445 final DetailAST field = frame.findField(name); 446 if (field != null) { 447 result = STRING.equals(getFieldType(field)); 448 } 449 break; 450 } 451 frame = getObjectFrame(frame.getParent()); 452 } 453 return result; 454 } 455 456 /** 457 * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 458 * @param frame to start the search from. 459 * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 460 */ 461 private static FieldFrame getObjectFrame(FieldFrame frame) { 462 FieldFrame objectFrame = frame; 463 while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) { 464 objectFrame = objectFrame.getParent(); 465 } 466 return objectFrame; 467 } 468 469 /** 470 * Check whether the field is declared before the method call in case of 471 * methods and initialization blocks. 472 * @param field field to check. 473 * @param objCalledOn object equals method called on. 474 * @return true if the field is declared before the method call. 475 */ 476 private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) { 477 boolean result = false; 478 // Required for pitest coverage. We should specify columnNo passing condition 479 // in such a way, so that the minimal possible distance between field and 480 // objCalledOn will be the maximal condition to pass this check. 481 // The minimal distance between objCalledOn and field (of type String) initialization 482 // is calculated as follows: 483 // String(6) + space(1) + variableName(1) + assign(1) + 484 // anotherStringVariableName(1) + semicolon(1) = 11 485 // Example: length of "String s=d;" is 11 symbols. 486 final int minimumSymbolsBetween = 11; 487 if (field.getLineNo() < objCalledOn.getLineNo() 488 || field.getLineNo() == objCalledOn.getLineNo() 489 && field.getColumnNo() + minimumSymbolsBetween <= objCalledOn.getColumnNo()) { 490 result = true; 491 } 492 return result; 493 } 494 495 /** 496 * Get field type. 497 * @param field to get the type from. 498 * @return type of the field. 499 */ 500 private static String getFieldType(DetailAST field) { 501 String fieldType = null; 502 final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE) 503 .findFirstToken(TokenTypes.IDENT); 504 if (identAst != null) { 505 fieldType = identAst.getText(); 506 } 507 return fieldType; 508 } 509 510 /** 511 * Holds the names of fields of a type. 512 */ 513 private static class FieldFrame { 514 515 /** Parent frame. */ 516 private final FieldFrame parent; 517 518 /** Set of frame's children. */ 519 private final Set<FieldFrame> children = new HashSet<>(); 520 521 /** Set of fields. */ 522 private final Set<DetailAST> fields = new HashSet<>(); 523 524 /** Set of equals calls. */ 525 private final Set<DetailAST> methodCalls = new HashSet<>(); 526 527 /** Name of the class, enum or enum constant declaration. */ 528 private String frameName; 529 530 /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */ 531 private boolean classOrEnumOrEnumConstDef; 532 533 /** 534 * Creates new frame. 535 * @param parent parent frame. 536 */ 537 FieldFrame(FieldFrame parent) { 538 this.parent = parent; 539 } 540 541 /** 542 * Set the frame name. 543 * @param frameName value to set. 544 */ 545 public void setFrameName(String frameName) { 546 this.frameName = frameName; 547 } 548 549 /** 550 * Getter for the frame name. 551 * @return frame name. 552 */ 553 public String getFrameName() { 554 return frameName; 555 } 556 557 /** 558 * Getter for the parent frame. 559 * @return parent frame. 560 */ 561 public FieldFrame getParent() { 562 return parent; 563 } 564 565 /** 566 * Getter for frame's children. 567 * @return children of this frame. 568 */ 569 public Set<FieldFrame> getChildren() { 570 return Collections.unmodifiableSet(children); 571 } 572 573 /** 574 * Add child frame to this frame. 575 * @param child frame to add. 576 */ 577 public void addChild(FieldFrame child) { 578 children.add(child); 579 } 580 581 /** 582 * Add field to this FieldFrame. 583 * @param field the ast of the field. 584 */ 585 public void addField(DetailAST field) { 586 fields.add(field); 587 } 588 589 /** 590 * Sets isClassOrEnum. 591 * @param value value to set. 592 */ 593 public void setClassOrEnumOrEnumConstDef(boolean value) { 594 classOrEnumOrEnumConstDef = value; 595 } 596 597 /** 598 * Getter for classOrEnumOrEnumConstDef. 599 * @return classOrEnumOrEnumConstDef. 600 */ 601 public boolean isClassOrEnumOrEnumConstDef() { 602 return classOrEnumOrEnumConstDef; 603 } 604 605 /** 606 * Add method call to this frame. 607 * @param methodCall METHOD_CALL ast. 608 */ 609 public void addMethodCall(DetailAST methodCall) { 610 methodCalls.add(methodCall); 611 } 612 613 /** 614 * Determines whether this FieldFrame contains the field. 615 * @param name name of the field to check. 616 * @return true if this FieldFrame contains instance field field. 617 */ 618 public DetailAST findField(String name) { 619 DetailAST resultField = null; 620 for (DetailAST field: fields) { 621 if (getFieldName(field).equals(name)) { 622 resultField = field; 623 break; 624 } 625 } 626 return resultField; 627 } 628 629 /** 630 * Getter for frame's method calls. 631 * @return method calls of this frame. 632 */ 633 public Set<DetailAST> getMethodCalls() { 634 return Collections.unmodifiableSet(methodCalls); 635 } 636 637 /** 638 * Get the name of the field. 639 * @param field to get the name from. 640 * @return name of the field. 641 */ 642 private static String getFieldName(DetailAST field) { 643 return field.findFirstToken(TokenTypes.IDENT).getText(); 644 } 645 646 } 647 648}