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 * @author Travis Schneeberger 060 * @author Vladislav Lisetskiy 061 */ 062@FileStatefulCheck 063public class EqualsAvoidNullCheck extends AbstractCheck { 064 065 /** 066 * A key is pointing to the warning message text in "messages.properties" 067 * file. 068 */ 069 public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null"; 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null"; 076 077 /** Method name for comparison. */ 078 private static final String EQUALS = "equals"; 079 080 /** Type name for comparison. */ 081 private static final String STRING = "String"; 082 083 /** Whether to process equalsIgnoreCase() invocations. */ 084 private boolean ignoreEqualsIgnoreCase; 085 086 /** Stack of sets of field names, one for each class of a set of nested classes. */ 087 private FieldFrame currentFrame; 088 089 @Override 090 public int[] getDefaultTokens() { 091 return getRequiredTokens(); 092 } 093 094 @Override 095 public int[] getAcceptableTokens() { 096 return getRequiredTokens(); 097 } 098 099 @Override 100 public int[] getRequiredTokens() { 101 return new int[] { 102 TokenTypes.METHOD_CALL, 103 TokenTypes.CLASS_DEF, 104 TokenTypes.METHOD_DEF, 105 TokenTypes.LITERAL_IF, 106 TokenTypes.LITERAL_FOR, 107 TokenTypes.LITERAL_WHILE, 108 TokenTypes.LITERAL_DO, 109 TokenTypes.LITERAL_CATCH, 110 TokenTypes.LITERAL_TRY, 111 TokenTypes.VARIABLE_DEF, 112 TokenTypes.PARAMETER_DEF, 113 TokenTypes.CTOR_DEF, 114 TokenTypes.SLIST, 115 TokenTypes.ENUM_DEF, 116 TokenTypes.ENUM_CONSTANT_DEF, 117 TokenTypes.LITERAL_NEW, 118 }; 119 } 120 121 /** 122 * Whether to ignore checking {@code String.equalsIgnoreCase(String)}. 123 * @param newValue whether to ignore checking 124 * {@code String.equalsIgnoreCase(String)}. 125 */ 126 public void setIgnoreEqualsIgnoreCase(boolean newValue) { 127 ignoreEqualsIgnoreCase = newValue; 128 } 129 130 @Override 131 public void beginTree(DetailAST rootAST) { 132 currentFrame = new FieldFrame(null); 133 } 134 135 @Override 136 public void visitToken(final DetailAST ast) { 137 switch (ast.getType()) { 138 case TokenTypes.VARIABLE_DEF: 139 case TokenTypes.PARAMETER_DEF: 140 currentFrame.addField(ast); 141 break; 142 case TokenTypes.METHOD_CALL: 143 processMethodCall(ast); 144 break; 145 case TokenTypes.SLIST: 146 processSlist(ast); 147 break; 148 case TokenTypes.LITERAL_NEW: 149 processLiteralNew(ast); 150 break; 151 default: 152 processFrame(ast); 153 } 154 } 155 156 @Override 157 public void leaveToken(DetailAST ast) { 158 final int astType = ast.getType(); 159 if (astType != TokenTypes.VARIABLE_DEF 160 && astType != TokenTypes.PARAMETER_DEF 161 && astType != TokenTypes.METHOD_CALL 162 && astType != TokenTypes.SLIST 163 && astType != TokenTypes.LITERAL_NEW 164 || astType == TokenTypes.LITERAL_NEW 165 && ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { 166 currentFrame = currentFrame.getParent(); 167 } 168 else if (astType == TokenTypes.SLIST) { 169 leaveSlist(ast); 170 } 171 } 172 173 @Override 174 public void finishTree(DetailAST ast) { 175 traverseFieldFrameTree(currentFrame); 176 } 177 178 /** 179 * Determine whether SLIST begins static or non-static block and add it as 180 * a frame in this case. 181 * @param ast SLIST ast. 182 */ 183 private void processSlist(DetailAST ast) { 184 final int parentType = ast.getParent().getType(); 185 if (parentType == TokenTypes.SLIST 186 || parentType == TokenTypes.STATIC_INIT 187 || parentType == TokenTypes.INSTANCE_INIT) { 188 final FieldFrame frame = new FieldFrame(currentFrame); 189 currentFrame.addChild(frame); 190 currentFrame = frame; 191 } 192 } 193 194 /** 195 * Determine whether SLIST begins static or non-static block. 196 * @param ast SLIST ast. 197 */ 198 private void leaveSlist(DetailAST ast) { 199 final int parentType = ast.getParent().getType(); 200 if (parentType == TokenTypes.SLIST 201 || parentType == TokenTypes.STATIC_INIT 202 || parentType == TokenTypes.INSTANCE_INIT) { 203 currentFrame = currentFrame.getParent(); 204 } 205 } 206 207 /** 208 * Process CLASS_DEF, METHOD_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, 209 * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF, ENUM_CONSTANT_DEF. 210 * @param ast processed ast. 211 */ 212 private void processFrame(DetailAST ast) { 213 final FieldFrame frame = new FieldFrame(currentFrame); 214 final int astType = ast.getType(); 215 if (astType == TokenTypes.CLASS_DEF 216 || astType == TokenTypes.ENUM_DEF 217 || astType == TokenTypes.ENUM_CONSTANT_DEF) { 218 frame.setClassOrEnumOrEnumConstDef(true); 219 frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText()); 220 } 221 currentFrame.addChild(frame); 222 currentFrame = frame; 223 } 224 225 /** 226 * Add the method call to the current frame if it should be processed. 227 * @param methodCall METHOD_CALL ast. 228 */ 229 private void processMethodCall(DetailAST methodCall) { 230 final DetailAST dot = methodCall.getFirstChild(); 231 if (dot.getType() == TokenTypes.DOT) { 232 final String methodName = dot.getLastChild().getText(); 233 if (EQUALS.equals(methodName) 234 || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) { 235 currentFrame.addMethodCall(methodCall); 236 } 237 } 238 } 239 240 /** 241 * Determine whether LITERAL_NEW is an anonymous class definition and add it as 242 * a frame in this case. 243 * @param ast LITERAL_NEW ast. 244 */ 245 private void processLiteralNew(DetailAST ast) { 246 if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { 247 final FieldFrame frame = new FieldFrame(currentFrame); 248 currentFrame.addChild(frame); 249 currentFrame = frame; 250 } 251 } 252 253 /** 254 * Traverse the tree of the field frames to check all equals method calls. 255 * @param frame to check method calls in. 256 */ 257 private void traverseFieldFrameTree(FieldFrame frame) { 258 for (FieldFrame child: frame.getChildren()) { 259 if (!child.getChildren().isEmpty()) { 260 traverseFieldFrameTree(child); 261 } 262 currentFrame = child; 263 child.getMethodCalls().forEach(this::checkMethodCall); 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 && isCalledOnStringFieldOrVariable(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 arg = skipVariableAssign(arg); 343 344 boolean argIsNotNull = false; 345 if (arg.getType() == TokenTypes.PLUS) { 346 DetailAST child = arg.getFirstChild(); 347 while (child != null 348 && !argIsNotNull) { 349 argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL 350 || child.getType() == TokenTypes.IDENT; 351 child = child.getNextSibling(); 352 } 353 } 354 355 return argIsNotNull 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 DetailAST result = currentAST; 367 if (currentAST.getType() == TokenTypes.ASSIGN 368 && currentAST.getFirstChild().getType() == TokenTypes.IDENT) { 369 result = currentAST.getFirstChild().getNextSibling(); 370 } 371 return result; 372 } 373 374 /** 375 * Determine, whether equals method is called on a field of String type. 376 * @param objCalledOn object ast. 377 * @return true if the object is of String type. 378 */ 379 private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) { 380 final boolean result; 381 final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling(); 382 if (previousSiblingAst == null) { 383 result = isStringFieldOrVariable(objCalledOn); 384 } 385 else { 386 if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) { 387 result = isStringFieldOrVariableFromThisInstance(objCalledOn); 388 } 389 else { 390 final String className = previousSiblingAst.getText(); 391 result = isStringFieldOrVariableFromClass(objCalledOn, className); 392 } 393 } 394 return result; 395 } 396 397 /** 398 * Whether the field or the variable is of String type. 399 * @param objCalledOn the field or the variable to check. 400 * @return true if the field or the variable is of String type. 401 */ 402 private boolean isStringFieldOrVariable(DetailAST objCalledOn) { 403 boolean result = false; 404 final String name = objCalledOn.getText(); 405 FieldFrame frame = currentFrame; 406 while (frame != null) { 407 final DetailAST field = frame.findField(name); 408 if (field != null 409 && (frame.isClassOrEnumOrEnumConstDef() 410 || checkLineNo(field, objCalledOn))) { 411 result = STRING.equals(getFieldType(field)); 412 break; 413 } 414 frame = frame.getParent(); 415 } 416 return result; 417 } 418 419 /** 420 * Whether the field or the variable from THIS instance is of String type. 421 * @param objCalledOn the field or the variable from THIS instance to check. 422 * @return true if the field or the variable from THIS instance is of String type. 423 */ 424 private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) { 425 boolean result = false; 426 final String name = objCalledOn.getText(); 427 final DetailAST field = getObjectFrame(currentFrame).findField(name); 428 if (field != null) { 429 result = STRING.equals(getFieldType(field)); 430 } 431 return result; 432 } 433 434 /** 435 * Whether the field or the variable from the specified class is of String type. 436 * @param objCalledOn the field or the variable from the specified class to check. 437 * @param className the name of the class to check in. 438 * @return true if the field or the variable from the specified class is of String type. 439 */ 440 private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn, 441 final String className) { 442 boolean result = false; 443 final String name = objCalledOn.getText(); 444 FieldFrame frame = getObjectFrame(currentFrame); 445 while (frame != null) { 446 if (className.equals(frame.getFrameName())) { 447 final DetailAST field = frame.findField(name); 448 if (field != null) { 449 result = STRING.equals(getFieldType(field)); 450 } 451 break; 452 } 453 frame = getObjectFrame(frame.getParent()); 454 } 455 return result; 456 } 457 458 /** 459 * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 460 * @param frame to start the search from. 461 * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. 462 */ 463 private static FieldFrame getObjectFrame(FieldFrame frame) { 464 FieldFrame objectFrame = frame; 465 while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) { 466 objectFrame = objectFrame.getParent(); 467 } 468 return objectFrame; 469 } 470 471 /** 472 * Check whether the field is declared before the method call in case of 473 * methods and initialization blocks. 474 * @param field field to check. 475 * @param objCalledOn object equals method called on. 476 * @return true if the field is declared before the method call. 477 */ 478 private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) { 479 boolean result = false; 480 // Required for pitest coverage. We should specify columnNo passing condition 481 // in such a way, so that the minimal possible distance between field and 482 // objCalledOn will be the maximal condition to pass this check. 483 // The minimal distance between objCalledOn and field (of type String) initialization 484 // is calculated as follows: 485 // String(6) + space(1) + variableName(1) + assign(1) + 486 // anotherStringVariableName(1) + semicolon(1) = 11 487 // Example: length of "String s=d;" is 11 symbols. 488 final int minimumSymbolsBetween = 11; 489 if (field.getLineNo() < objCalledOn.getLineNo() 490 || field.getLineNo() == objCalledOn.getLineNo() 491 && field.getColumnNo() + minimumSymbolsBetween <= objCalledOn.getColumnNo()) { 492 result = true; 493 } 494 return result; 495 } 496 497 /** 498 * Get field type. 499 * @param field to get the type from. 500 * @return type of the field. 501 */ 502 private static String getFieldType(DetailAST field) { 503 String fieldType = null; 504 final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE) 505 .findFirstToken(TokenTypes.IDENT); 506 if (identAst != null) { 507 fieldType = identAst.getText(); 508 } 509 return fieldType; 510 } 511 512 /** 513 * Holds the names of fields of a type. 514 */ 515 private static class FieldFrame { 516 517 /** Parent frame. */ 518 private final FieldFrame parent; 519 520 /** Set of frame's children. */ 521 private final Set<FieldFrame> children = new HashSet<>(); 522 523 /** Set of fields. */ 524 private final Set<DetailAST> fields = new HashSet<>(); 525 526 /** Set of equals calls. */ 527 private final Set<DetailAST> methodCalls = new HashSet<>(); 528 529 /** Name of the class, enum or enum constant declaration. */ 530 private String frameName; 531 532 /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */ 533 private boolean classOrEnumOrEnumConstDef; 534 535 /** 536 * Creates new frame. 537 * @param parent parent frame. 538 */ 539 FieldFrame(FieldFrame parent) { 540 this.parent = parent; 541 } 542 543 /** 544 * Set the frame name. 545 * @param frameName value to set. 546 */ 547 public void setFrameName(String frameName) { 548 this.frameName = frameName; 549 } 550 551 /** 552 * Getter for the frame name. 553 * @return frame name. 554 */ 555 public String getFrameName() { 556 return frameName; 557 } 558 559 /** 560 * Getter for the parent frame. 561 * @return parent frame. 562 */ 563 public FieldFrame getParent() { 564 return parent; 565 } 566 567 /** 568 * Getter for frame's children. 569 * @return children of this frame. 570 */ 571 public Set<FieldFrame> getChildren() { 572 return Collections.unmodifiableSet(children); 573 } 574 575 /** 576 * Add child frame to this frame. 577 * @param child frame to add. 578 */ 579 public void addChild(FieldFrame child) { 580 children.add(child); 581 } 582 583 /** 584 * Add field to this FieldFrame. 585 * @param field the ast of the field. 586 */ 587 public void addField(DetailAST field) { 588 fields.add(field); 589 } 590 591 /** 592 * Sets isClassOrEnum. 593 * @param value value to set. 594 */ 595 public void setClassOrEnumOrEnumConstDef(boolean value) { 596 classOrEnumOrEnumConstDef = value; 597 } 598 599 /** 600 * Getter for classOrEnumOrEnumConstDef. 601 * @return classOrEnumOrEnumConstDef. 602 */ 603 public boolean isClassOrEnumOrEnumConstDef() { 604 return classOrEnumOrEnumConstDef; 605 } 606 607 /** 608 * Add method call to this frame. 609 * @param methodCall METHOD_CALL ast. 610 */ 611 public void addMethodCall(DetailAST methodCall) { 612 methodCalls.add(methodCall); 613 } 614 615 /** 616 * Determines whether this FieldFrame contains the field. 617 * @param name name of the field to check. 618 * @return true if this FieldFrame contains instance field field. 619 */ 620 public DetailAST findField(String name) { 621 DetailAST resultField = null; 622 for (DetailAST field: fields) { 623 if (getFieldName(field).equals(name)) { 624 resultField = field; 625 break; 626 } 627 } 628 return resultField; 629 } 630 631 /** 632 * Getter for frame's method calls. 633 * @return method calls of this frame. 634 */ 635 public Set<DetailAST> getMethodCalls() { 636 return Collections.unmodifiableSet(methodCalls); 637 } 638 639 /** 640 * Get the name of the field. 641 * @param field to get the name from. 642 * @return name of the field. 643 */ 644 private static String getFieldName(DetailAST field) { 645 return field.findFirstToken(TokenTypes.IDENT).getText(); 646 } 647 648 } 649 650}