| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2018 the original author or authors. | |
| 4 | // | |
| 5 | // This library is free software; you can redistribute it and/or | |
| 6 | // modify it under the terms of the GNU Lesser General Public | |
| 7 | // License as published by the Free Software Foundation; either | |
| 8 | // version 2.1 of the License, or (at your option) any later version. | |
| 9 | // | |
| 10 | // This library is distributed in the hope that it will be useful, | |
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 13 | // Lesser General Public License for more details. | |
| 14 | // | |
| 15 | // You should have received a copy of the GNU Lesser General Public | |
| 16 | // License along with this library; if not, write to the Free Software | |
| 17 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 18 | //////////////////////////////////////////////////////////////////////////////// | |
| 19 | ||
| 20 | package com.puppycrawl.tools.checkstyle.checks.coding; | |
| 21 | ||
| 22 | import java.util.Collections; | |
| 23 | import java.util.HashSet; | |
| 24 | import java.util.Set; | |
| 25 | ||
| 26 | import com.puppycrawl.tools.checkstyle.FileStatefulCheck; | |
| 27 | import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | |
| 28 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
| 29 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
| 30 | ||
| 31 | /** | |
| 32 | * Checks that any combination of String literals | |
| 33 | * is on the left side of an equals() comparison. | |
| 34 | * Also checks for String literals assigned to some field | |
| 35 | * (such as {@code someString.equals(anotherString = "text")}). | |
| 36 | * | |
| 37 | * <p>Rationale: Calling the equals() method on String literals | |
| 38 | * will avoid a potential NullPointerException. Also, it is | |
| 39 | * pretty common to see null check right before equals comparisons | |
| 40 | * which is not necessary in the below example. | |
| 41 | * | |
| 42 | * <p>For example: | |
| 43 | * | |
| 44 | * <pre> | |
| 45 | * {@code | |
| 46 | * String nullString = null; | |
| 47 | * nullString.equals("My_Sweet_String"); | |
| 48 | * } | |
| 49 | * </pre> | |
| 50 | * should be refactored to | |
| 51 | * | |
| 52 | * <pre> | |
| 53 | * {@code | |
| 54 | * String nullString = null; | |
| 55 | * "My_Sweet_String".equals(nullString); | |
| 56 | * } | |
| 57 | * </pre> | |
| 58 | * | |
| 59 | * @author Travis Schneeberger | |
| 60 | * @author Vladislav Lisetskiy | |
| 61 | */ | |
| 62 | @FileStatefulCheck | |
| 63 | public class EqualsAvoidNullCheck extends AbstractCheck { | |
| 64 | ||
| 65 | /** | |
| 66 | * A key is pointing to the warning message text in "messages.properties" | |
| 67 | * file. | |
| 68 | */ | |
| 69 | public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null"; | |
| 70 | ||
| 71 | /** | |
| 72 | * A key is pointing to the warning message text in "messages.properties" | |
| 73 | * file. | |
| 74 | */ | |
| 75 | public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null"; | |
| 76 | ||
| 77 | /** Method name for comparison. */ | |
| 78 | private static final String EQUALS = "equals"; | |
| 79 | ||
| 80 | /** Type name for comparison. */ | |
| 81 | private static final String STRING = "String"; | |
| 82 | ||
| 83 | /** Whether to process equalsIgnoreCase() invocations. */ | |
| 84 | private boolean ignoreEqualsIgnoreCase; | |
| 85 | ||
| 86 | /** Stack of sets of field names, one for each class of a set of nested classes. */ | |
| 87 | private FieldFrame currentFrame; | |
| 88 | ||
| 89 | @Override | |
| 90 | public int[] getDefaultTokens() { | |
| 91 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
| 92 | } | |
| 93 | ||
| 94 | @Override | |
| 95 | public int[] getAcceptableTokens() { | |
| 96 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
| 97 | } | |
| 98 | ||
| 99 | @Override | |
| 100 | public int[] getRequiredTokens() { | |
| 101 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::addField → KILLED |
currentFrame.addField(ast); |
| 141 | break; | |
| 142 | case TokenTypes.METHOD_CALL: | |
| 143 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::processMethodCall → KILLED |
processMethodCall(ast); |
| 144 | break; | |
| 145 | case TokenTypes.SLIST: | |
| 146 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::processSlist → KILLED |
processSlist(ast); |
| 147 | break; | |
| 148 | case TokenTypes.LITERAL_NEW: | |
| 149 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::processLiteralNew → KILLED |
processLiteralNew(ast); |
| 150 | break; | |
| 151 | default: | |
| 152 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::processFrame → KILLED |
processFrame(ast); |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | @Override | |
| 157 | public void leaveToken(DetailAST ast) { | |
| 158 | final int astType = ast.getType(); | |
| 159 |
6
1. leaveToken : negated conditional → KILLED 2. leaveToken : negated conditional → KILLED 3. leaveToken : negated conditional → KILLED 4. leaveToken : negated conditional → KILLED 5. leaveToken : negated conditional → KILLED 6. leaveToken : negated conditional → KILLED |
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 |
1
1. leaveToken : negated conditional → KILLED |
&& ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { |
| 166 | currentFrame = currentFrame.getParent(); | |
| 167 | } | |
| 168 |
1
1. leaveToken : negated conditional → KILLED |
else if (astType == TokenTypes.SLIST) { |
| 169 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::leaveSlist → KILLED |
leaveSlist(ast); |
| 170 | } | |
| 171 | } | |
| 172 | ||
| 173 | @Override | |
| 174 | public void finishTree(DetailAST ast) { | |
| 175 |
1
1. finishTree : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::traverseFieldFrameTree → KILLED |
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 |
3
1. processSlist : negated conditional → KILLED 2. processSlist : negated conditional → KILLED 3. processSlist : negated conditional → KILLED |
if (parentType == TokenTypes.SLIST |
| 186 | || parentType == TokenTypes.STATIC_INIT | |
| 187 | || parentType == TokenTypes.INSTANCE_INIT) { | |
| 188 | final FieldFrame frame = new FieldFrame(currentFrame); | |
| 189 |
1
1. processSlist : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::addChild → KILLED |
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 |
3
1. leaveSlist : negated conditional → KILLED 2. leaveSlist : negated conditional → KILLED 3. leaveSlist : negated conditional → KILLED |
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 |
3
1. processFrame : negated conditional → KILLED 2. processFrame : negated conditional → KILLED 3. processFrame : negated conditional → KILLED |
if (astType == TokenTypes.CLASS_DEF |
| 216 | || astType == TokenTypes.ENUM_DEF | |
| 217 | || astType == TokenTypes.ENUM_CONSTANT_DEF) { | |
| 218 |
1
1. processFrame : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::setClassOrEnumOrEnumConstDef → KILLED |
frame.setClassOrEnumOrEnumConstDef(true); |
| 219 |
1
1. processFrame : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::setFrameName → KILLED |
frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText()); |
| 220 | } | |
| 221 |
1
1. processFrame : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::addChild → KILLED |
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 |
1
1. processMethodCall : negated conditional → KILLED |
if (dot.getType() == TokenTypes.DOT) { |
| 232 | final String methodName = dot.getLastChild().getText(); | |
| 233 |
2
1. processMethodCall : negated conditional → KILLED 2. processMethodCall : negated conditional → KILLED |
if (EQUALS.equals(methodName) |
| 234 |
1
1. processMethodCall : negated conditional → KILLED |
|| !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) { |
| 235 |
1
1. processMethodCall : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::addMethodCall → KILLED |
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 |
1
1. processLiteralNew : negated conditional → KILLED |
if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) { |
| 247 | final FieldFrame frame = new FieldFrame(currentFrame); | |
| 248 |
1
1. processLiteralNew : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::addChild → KILLED |
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 |
1
1. traverseFieldFrameTree : negated conditional → KILLED |
if (!child.getChildren().isEmpty()) { |
| 260 |
1
1. traverseFieldFrameTree : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::traverseFieldFrameTree → KILLED |
traverseFieldFrameTree(child); |
| 261 | } | |
| 262 | currentFrame = child; | |
| 263 |
1
1. traverseFieldFrameTree : removed call to java/util/Set::forEach → KILLED |
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 |
1
1. checkMethodCall : negated conditional → KILLED |
if (objCalledOn.getType() == TokenTypes.DOT) { |
| 274 | objCalledOn = objCalledOn.getLastChild(); | |
| 275 | } | |
| 276 | final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild(); | |
| 277 |
1
1. checkMethodCall : negated conditional → KILLED |
if (isObjectValid(objCalledOn) |
| 278 |
1
1. checkMethodCall : negated conditional → KILLED |
&& containsOneArgument(methodCall) |
| 279 |
1
1. checkMethodCall : negated conditional → KILLED |
&& containsAllSafeTokens(expr) |
| 280 |
1
1. checkMethodCall : negated conditional → KILLED |
&& isCalledOnStringFieldOrVariable(objCalledOn)) { |
| 281 | final String methodName = methodCall.getFirstChild().getLastChild().getText(); | |
| 282 |
1
1. checkMethodCall : negated conditional → KILLED |
if (EQUALS.equals(methodName)) { |
| 283 |
1
1. checkMethodCall : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::log → KILLED |
log(methodCall.getLineNo(), methodCall.getColumnNo(), |
| 284 | MSG_EQUALS_AVOID_NULL); | |
| 285 | } | |
| 286 | else { | |
| 287 |
1
1. checkMethodCall : removed call to com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::log → KILLED |
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 |
1
1. isObjectValid : negated conditional → KILLED |
if (previousSibling != null |
| 303 |
1
1. isObjectValid : negated conditional → KILLED |
&& previousSibling.getType() == TokenTypes.DOT) { |
| 304 | result = false; | |
| 305 | } | |
| 306 |
1
1. isObjectValid : negated conditional → KILLED |
if (isStringLiteral(objCalledOn)) { |
| 307 | result = false; | |
| 308 | } | |
| 309 |
1
1. isObjectValid : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
2
1. isStringLiteral : negated conditional → KILLED 2. isStringLiteral : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return objCalledOn.getType() == TokenTypes.STRING_LITERAL |
| 320 |
1
1. isStringLiteral : negated conditional → KILLED |
|| 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 |
2
1. containsOneArgument : negated conditional → KILLED 2. containsOneArgument : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. containsAllSafeTokens : negated conditional → KILLED |
if (arg.getType() == TokenTypes.PLUS) { |
| 346 | DetailAST child = arg.getFirstChild(); | |
| 347 |
2
1. containsAllSafeTokens : negated conditional → KILLED 2. containsAllSafeTokens : negated conditional → KILLED |
while (child != null |
| 348 | && !argIsNotNull) { | |
| 349 |
1
1. containsAllSafeTokens : negated conditional → KILLED |
argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL |
| 350 |
1
1. containsAllSafeTokens : negated conditional → KILLED |
|| child.getType() == TokenTypes.IDENT; |
| 351 | child = child.getNextSibling(); | |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 |
2
1. containsAllSafeTokens : negated conditional → KILLED 2. containsAllSafeTokens : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return argIsNotNull |
| 356 |
1
1. containsAllSafeTokens : negated conditional → KILLED |
|| !arg.branchContains(TokenTypes.IDENT) |
| 357 |
1
1. containsAllSafeTokens : negated conditional → KILLED |
&& !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 |
1
1. skipVariableAssign : negated conditional → KILLED |
if (currentAST.getType() == TokenTypes.ASSIGN |
| 368 |
1
1. skipVariableAssign : negated conditional → KILLED |
&& currentAST.getFirstChild().getType() == TokenTypes.IDENT) { |
| 369 | result = currentAST.getFirstChild().getNextSibling(); | |
| 370 | } | |
| 371 |
1
1. skipVariableAssign : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::skipVariableAssign to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. isCalledOnStringFieldOrVariable : negated conditional → KILLED |
if (previousSiblingAst == null) { |
| 383 | result = isStringFieldOrVariable(objCalledOn); | |
| 384 | } | |
| 385 | else { | |
| 386 |
1
1. isCalledOnStringFieldOrVariable : negated conditional → KILLED |
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 |
1
1. isCalledOnStringFieldOrVariable : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. isStringFieldOrVariable : negated conditional → KILLED |
while (frame != null) { |
| 407 | final DetailAST field = frame.findField(name); | |
| 408 |
1
1. isStringFieldOrVariable : negated conditional → KILLED |
if (field != null |
| 409 |
1
1. isStringFieldOrVariable : negated conditional → KILLED |
&& (frame.isClassOrEnumOrEnumConstDef() |
| 410 |
1
1. isStringFieldOrVariable : negated conditional → KILLED |
|| checkLineNo(field, objCalledOn))) { |
| 411 | result = STRING.equals(getFieldType(field)); | |
| 412 | break; | |
| 413 | } | |
| 414 | frame = frame.getParent(); | |
| 415 | } | |
| 416 |
1
1. isStringFieldOrVariable : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. isStringFieldOrVariableFromThisInstance : negated conditional → KILLED |
if (field != null) { |
| 429 | result = STRING.equals(getFieldType(field)); | |
| 430 | } | |
| 431 |
1
1. isStringFieldOrVariableFromThisInstance : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. isStringFieldOrVariableFromClass : negated conditional → KILLED |
while (frame != null) { |
| 446 |
1
1. isStringFieldOrVariableFromClass : negated conditional → KILLED |
if (className.equals(frame.getFrameName())) { |
| 447 | final DetailAST field = frame.findField(name); | |
| 448 |
1
1. isStringFieldOrVariableFromClass : negated conditional → KILLED |
if (field != null) { |
| 449 | result = STRING.equals(getFieldType(field)); | |
| 450 | } | |
| 451 | break; | |
| 452 | } | |
| 453 | frame = getObjectFrame(frame.getParent()); | |
| 454 | } | |
| 455 |
1
1. isStringFieldOrVariableFromClass : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
2
1. getObjectFrame : negated conditional → KILLED 2. getObjectFrame : negated conditional → KILLED |
while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) { |
| 466 | objectFrame = objectFrame.getParent(); | |
| 467 | } | |
| 468 |
1
1. getObjectFrame : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::getObjectFrame to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
2
1. checkLineNo : changed conditional boundary → KILLED 2. checkLineNo : negated conditional → KILLED |
if (field.getLineNo() < objCalledOn.getLineNo() |
| 490 |
1
1. checkLineNo : negated conditional → KILLED |
|| field.getLineNo() == objCalledOn.getLineNo() |
| 491 |
3
1. checkLineNo : Replaced integer addition with subtraction → SURVIVED 2. checkLineNo : changed conditional boundary → KILLED 3. checkLineNo : negated conditional → KILLED |
&& field.getColumnNo() + minimumSymbolsBetween <= objCalledOn.getColumnNo()) { |
| 492 | result = true; | |
| 493 | } | |
| 494 |
1
1. checkLineNo : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. getFieldType : negated conditional → KILLED |
if (identAst != null) { |
| 507 | fieldType = identAst.getText(); | |
| 508 | } | |
| 509 |
1
1. getFieldType : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck::getFieldType to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. getFrameName : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::getFrameName to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return frameName; |
| 557 | } | |
| 558 | ||
| 559 | /** | |
| 560 | * Getter for the parent frame. | |
| 561 | * @return parent frame. | |
| 562 | */ | |
| 563 | public FieldFrame getParent() { | |
| 564 |
1
1. getParent : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::getParent to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return parent; |
| 565 | } | |
| 566 | ||
| 567 | /** | |
| 568 | * Getter for frame's children. | |
| 569 | * @return children of this frame. | |
| 570 | */ | |
| 571 | public Set<FieldFrame> getChildren() { | |
| 572 |
1
1. getChildren : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::getChildren to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. isClassOrEnumOrEnumConstDef : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
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 |
1
1. findField : negated conditional → KILLED |
if (getFieldName(field).equals(name)) { |
| 624 | resultField = field; | |
| 625 | break; | |
| 626 | } | |
| 627 | } | |
| 628 |
1
1. findField : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::findField to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. getMethodCalls : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::getMethodCalls to ( if (x != null) null else throw new RuntimeException ) → KILLED |
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 |
1
1. getFieldName : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/EqualsAvoidNullCheck$FieldFrame::getFieldName to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return field.findFirstToken(TokenTypes.IDENT).getText(); |
| 646 | } | |
| 647 | ||
| 648 | } | |
| 649 | ||
| 650 | } | |
Mutations | ||
| 91 |
1.1 |
|
| 96 |
1.1 |
|
| 101 |
1.1 |
|
| 140 |
1.1 |
|
| 143 |
1.1 |
|
| 146 |
1.1 |
|
| 149 |
1.1 |
|
| 152 |
1.1 |
|
| 159 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 165 |
1.1 |
|
| 168 |
1.1 |
|
| 169 |
1.1 |
|
| 175 |
1.1 |
|
| 185 |
1.1 2.2 3.3 |
|
| 189 |
1.1 |
|
| 200 |
1.1 2.2 3.3 |
|
| 215 |
1.1 2.2 3.3 |
|
| 218 |
1.1 |
|
| 219 |
1.1 |
|
| 221 |
1.1 |
|
| 231 |
1.1 |
|
| 233 |
1.1 2.2 |
|
| 234 |
1.1 |
|
| 235 |
1.1 |
|
| 246 |
1.1 |
|
| 248 |
1.1 |
|
| 259 |
1.1 |
|
| 260 |
1.1 |
|
| 263 |
1.1 |
|
| 273 |
1.1 |
|
| 277 |
1.1 |
|
| 278 |
1.1 |
|
| 279 |
1.1 |
|
| 280 |
1.1 |
|
| 282 |
1.1 |
|
| 283 |
1.1 |
|
| 287 |
1.1 |
|
| 302 |
1.1 |
|
| 303 |
1.1 |
|
| 306 |
1.1 |
|
| 309 |
1.1 |
|
| 319 |
1.1 2.2 |
|
| 320 |
1.1 |
|
| 331 |
1.1 2.2 |
|
| 345 |
1.1 |
|
| 347 |
1.1 2.2 |
|
| 349 |
1.1 |
|
| 350 |
1.1 |
|
| 355 |
1.1 2.2 |
|
| 356 |
1.1 |
|
| 357 |
1.1 |
|
| 367 |
1.1 |
|
| 368 |
1.1 |
|
| 371 |
1.1 |
|
| 382 |
1.1 |
|
| 386 |
1.1 |
|
| 394 |
1.1 |
|
| 406 |
1.1 |
|
| 408 |
1.1 |
|
| 409 |
1.1 |
|
| 410 |
1.1 |
|
| 416 |
1.1 |
|
| 428 |
1.1 |
|
| 431 |
1.1 |
|
| 445 |
1.1 |
|
| 446 |
1.1 |
|
| 448 |
1.1 |
|
| 455 |
1.1 |
|
| 465 |
1.1 2.2 |
|
| 468 |
1.1 |
|
| 489 |
1.1 2.2 |
|
| 490 |
1.1 |
|
| 491 |
1.1 2.2 3.3 |
|
| 494 |
1.1 |
|
| 506 |
1.1 |
|
| 509 |
1.1 |
|
| 556 |
1.1 |
|
| 564 |
1.1 |
|
| 572 |
1.1 |
|
| 604 |
1.1 |
|
| 623 |
1.1 |
|
| 628 |
1.1 |
|
| 636 |
1.1 |
|
| 645 |
1.1 |