001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.metrics; 021 022import java.math.BigInteger; 023import java.util.ArrayDeque; 024import java.util.Deque; 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 * <p> 033 * Checks the NPATH complexity against a specified limit. 034 * </p> 035 * <p> 036 * The NPATH metric computes the number of possible execution paths through a 037 * function(method). It takes into account the nesting of conditional statements 038 * and multi-part boolean expressions (A && B, C || D, E ? F :G and 039 * their combinations). 040 * </p> 041 * <p> 042 * The NPATH metric was designed base on Cyclomatic complexity to avoid problem 043 * of Cyclomatic complexity metric like nesting level within a function(method). 044 * </p> 045 * <p> 046 * Metric was described at <a href="http://dl.acm.org/citation.cfm?id=42379"> 047 * "NPATH: a measure of execution pathcomplexity and its applications"</a>. 048 * If you need detailed description of algorithm, please read that article, 049 * it is well written and have number of examples and details. 050 * </p> 051 * <p> 052 * Here is some quotes: 053 * </p> 054 * <blockquote> 055 * An NPATH threshold value of 200 has been established for a function. 056 * The value 200 is based on studies done at AT&T Bell Laboratories [1988 year]. 057 * </blockquote> 058 * <blockquote> 059 * Some of the most effective methods of reducing the NPATH value include: 060 * <ul> 061 * <li> 062 * distributing functionality; 063 * </li> 064 * <li> 065 * implementing multiple if statements as a switch statement; 066 * </li> 067 * <li> 068 * creating a separate function for logical expressions with a high count of 069 * variables and (&&) and or (||) operators. 070 * </li> 071 * </ul> 072 * </blockquote> 073 * <blockquote> 074 * Although strategies to reduce the NPATH complexity of functions are important, 075 * care must be taken not to distort the logical clarity of the software by 076 * applying a strategy to reduce the complexity of functions. That is, there is 077 * a point of diminishing return beyond which a further attempt at reduction of 078 * complexity distorts the logical clarity of the system structure. 079 * </blockquote> 080 * <table> 081 * <caption>Examples</caption> 082 * <thead><tr><th>Structure</th><th>Complexity expression</th></tr></thead> 083 * <tr><td>if ([expr]) { [if-range] }</td><td>NP(if-range) + 1 + NP(expr)</td></tr> 084 * <tr><td>if ([expr]) { [if-range] } else { [else-range] }</td> 085 * <td>NP(if-range)+ NP(else-range) + NP(expr)</td></tr> 086 * <tr><td>while ([expr]) { [while-range] }</td><td>NP(while-range) + NP(expr) + 1</td></tr> 087 * <tr><td>do { [do-range] } while ([expr])</td><td>NP(do-range) + NP(expr) + 1</td></tr> 088 * <tr><td>for([expr1]; [expr2]; [expr3]) { [for-range] }</td> 089 * <td>NP(for-range) + NP(expr1)+ NP(expr2) + NP(expr3) + 1</td></tr> 090 * <tr><td>switch ([expr]) { case : [case-range] default: [default-range] }</td> 091 * <td>S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr)</td></tr> 092 * <tr><td>[expr1] ? [expr2] : [expr3]</td><td>NP(expr1) + NP(expr2) + NP(expr3) + 2</td></tr> 093 * <tr><td>goto label</td><td>1</td></tr><tr><td>break</td><td>1</td></tr> 094 * <tr><td>Expressions</td> 095 * <td>Number of && and || operators in expression. No operators - 0</td></tr> 096 * <tr><td>continue</td><td>1</td></tr><tr><td>return</td><td>1</td></tr> 097 * <tr><td>Statement (even sequential statements)</td><td>1</td></tr> 098 * <tr><td>Empty block {}</td><td>1</td></tr><tr><td>Function call</td><td>1</td> 099 * </tr><tr><td>Function(Method) declaration or Block</td><td>P(i=1:i=N)NP(Statement[i])</td></tr> 100 * </table> 101 * <p> 102 * <b>Rationale:</b> Nejmeh says that his group had an informal NPATH limit of 103 * 200 on individual routines; functions(methods) that exceeded this value were 104 * candidates for further decomposition - or at least a closer look. 105 * <b>Please do not be fanatic with limit 200</b> - choose number that suites 106 * your project style. Limit 200 is empirical number base on some sources of at 107 * AT&T Bell Laboratories of 1988 year. 108 * </p> 109 * <ul> 110 * <li> 111 * Property {@code max} - Specify the maximum threshold allowed. 112 * Default value is {@code 200}. 113 * </li> 114 * </ul> 115 * <p> 116 * To configure the check: 117 * </p> 118 * <pre> 119 * <module name="NPathComplexity"/> 120 * </pre> 121 * <p> 122 * To configure the check with a threshold of 1000: 123 * </p> 124 * <pre> 125 * <module name="NPathComplexity"> 126 * <property name="max" value="1000"/> 127 * </module> 128 * </pre> 129 * 130 * @since 3.4 131 */ 132// -@cs[AbbreviationAsWordInName] Can't change check name 133@FileStatefulCheck 134public final class NPathComplexityCheck extends AbstractCheck { 135 136 /** 137 * A key is pointing to the warning message text in "messages.properties" 138 * file. 139 */ 140 public static final String MSG_KEY = "npathComplexity"; 141 142 /** Default allowed complexity. */ 143 private static final int DEFAULT_MAX = 200; 144 145 /** The initial current value. */ 146 private static final BigInteger INITIAL_VALUE = BigInteger.ZERO; 147 148 /** 149 * Stack of NP values for ranges. 150 */ 151 private final Deque<BigInteger> rangeValues = new ArrayDeque<>(); 152 153 /** Stack of NP values for expressions. */ 154 private final Deque<Integer> expressionValues = new ArrayDeque<>(); 155 156 /** Stack of belongs to range values for question operator. */ 157 private final Deque<Boolean> afterValues = new ArrayDeque<>(); 158 159 /** 160 * Range of the last processed expression. Used for checking that ternary operation 161 * which is a part of expression won't be processed for second time. 162 */ 163 private final TokenEnd processingTokenEnd = new TokenEnd(); 164 165 /** NP value for current range. */ 166 private BigInteger currentRangeValue = INITIAL_VALUE; 167 168 /** Specify the maximum threshold allowed. */ 169 private int max = DEFAULT_MAX; 170 171 /** True, when branch is visited, but not leaved. */ 172 private boolean branchVisited; 173 174 /** 175 * Setter to specify the maximum threshold allowed. 176 * 177 * @param max the maximum threshold 178 */ 179 public void setMax(int max) { 180 this.max = max; 181 } 182 183 @Override 184 public int[] getDefaultTokens() { 185 return getRequiredTokens(); 186 } 187 188 @Override 189 public int[] getAcceptableTokens() { 190 return getRequiredTokens(); 191 } 192 193 @Override 194 public int[] getRequiredTokens() { 195 return new int[] { 196 TokenTypes.CTOR_DEF, 197 TokenTypes.METHOD_DEF, 198 TokenTypes.STATIC_INIT, 199 TokenTypes.INSTANCE_INIT, 200 TokenTypes.LITERAL_WHILE, 201 TokenTypes.LITERAL_DO, 202 TokenTypes.LITERAL_FOR, 203 TokenTypes.LITERAL_IF, 204 TokenTypes.LITERAL_ELSE, 205 TokenTypes.LITERAL_SWITCH, 206 TokenTypes.CASE_GROUP, 207 TokenTypes.LITERAL_TRY, 208 TokenTypes.LITERAL_CATCH, 209 TokenTypes.QUESTION, 210 TokenTypes.LITERAL_RETURN, 211 TokenTypes.LITERAL_DEFAULT, 212 }; 213 } 214 215 @Override 216 public void beginTree(DetailAST rootAST) { 217 rangeValues.clear(); 218 expressionValues.clear(); 219 afterValues.clear(); 220 processingTokenEnd.reset(); 221 currentRangeValue = INITIAL_VALUE; 222 branchVisited = false; 223 } 224 225 @Override 226 public void visitToken(DetailAST ast) { 227 switch (ast.getType()) { 228 case TokenTypes.LITERAL_IF: 229 case TokenTypes.LITERAL_SWITCH: 230 case TokenTypes.LITERAL_WHILE: 231 case TokenTypes.LITERAL_DO: 232 case TokenTypes.LITERAL_FOR: 233 visitConditional(ast, 1); 234 break; 235 case TokenTypes.QUESTION: 236 visitUnitaryOperator(ast, 2); 237 break; 238 case TokenTypes.LITERAL_RETURN: 239 visitUnitaryOperator(ast, 0); 240 break; 241 case TokenTypes.CASE_GROUP: 242 final int caseNumber = countCaseTokens(ast); 243 branchVisited = true; 244 pushValue(caseNumber); 245 break; 246 case TokenTypes.LITERAL_ELSE: 247 branchVisited = true; 248 if (currentRangeValue.equals(BigInteger.ZERO)) { 249 currentRangeValue = BigInteger.ONE; 250 } 251 pushValue(0); 252 break; 253 case TokenTypes.LITERAL_TRY: 254 case TokenTypes.LITERAL_CATCH: 255 case TokenTypes.LITERAL_DEFAULT: 256 pushValue(1); 257 break; 258 case TokenTypes.CTOR_DEF: 259 case TokenTypes.METHOD_DEF: 260 case TokenTypes.INSTANCE_INIT: 261 case TokenTypes.STATIC_INIT: 262 pushValue(0); 263 break; 264 default: 265 break; 266 } 267 } 268 269 @Override 270 public void leaveToken(DetailAST ast) { 271 switch (ast.getType()) { 272 case TokenTypes.LITERAL_WHILE: 273 case TokenTypes.LITERAL_DO: 274 case TokenTypes.LITERAL_FOR: 275 case TokenTypes.LITERAL_IF: 276 case TokenTypes.LITERAL_SWITCH: 277 leaveConditional(); 278 break; 279 case TokenTypes.LITERAL_TRY: 280 leaveMultiplyingConditional(); 281 break; 282 case TokenTypes.LITERAL_RETURN: 283 case TokenTypes.QUESTION: 284 leaveUnitaryOperator(); 285 break; 286 case TokenTypes.LITERAL_CATCH: 287 leaveAddingConditional(); 288 break; 289 case TokenTypes.LITERAL_DEFAULT: 290 leaveBranch(); 291 break; 292 case TokenTypes.LITERAL_ELSE: 293 case TokenTypes.CASE_GROUP: 294 leaveBranch(); 295 branchVisited = false; 296 break; 297 case TokenTypes.CTOR_DEF: 298 case TokenTypes.METHOD_DEF: 299 case TokenTypes.INSTANCE_INIT: 300 case TokenTypes.STATIC_INIT: 301 leaveMethodDef(ast); 302 break; 303 default: 304 break; 305 } 306 } 307 308 /** 309 * Visits if, while, do-while, for and switch tokens - all of them have expression in 310 * parentheses which is used for calculation. 311 * @param ast visited token. 312 * @param basicBranchingFactor default number of branches added. 313 */ 314 private void visitConditional(DetailAST ast, int basicBranchingFactor) { 315 int expressionValue = basicBranchingFactor; 316 DetailAST bracketed; 317 for (bracketed = ast.findFirstToken(TokenTypes.LPAREN).getNextSibling(); 318 bracketed.getType() != TokenTypes.RPAREN; 319 bracketed = bracketed.getNextSibling()) { 320 expressionValue += countConditionalOperators(bracketed); 321 } 322 processingTokenEnd.setToken(bracketed); 323 pushValue(expressionValue); 324 } 325 326 /** 327 * Visits ternary operator (?:) and return tokens. They differ from those processed by 328 * visitConditional method in that their expression isn't bracketed. 329 * @param ast visited token. 330 * @param basicBranchingFactor number of branches inherently added by this token. 331 */ 332 private void visitUnitaryOperator(DetailAST ast, int basicBranchingFactor) { 333 final boolean isAfter = processingTokenEnd.isAfter(ast); 334 afterValues.push(isAfter); 335 if (!isAfter) { 336 processingTokenEnd.setToken(getLastToken(ast)); 337 final int expressionValue = basicBranchingFactor + countConditionalOperators(ast); 338 pushValue(expressionValue); 339 } 340 } 341 342 /** 343 * Leaves ternary operator (?:) and return tokens. 344 */ 345 private void leaveUnitaryOperator() { 346 if (!afterValues.pop()) { 347 final Values valuePair = popValue(); 348 BigInteger basicRangeValue = valuePair.getRangeValue(); 349 BigInteger expressionValue = valuePair.getExpressionValue(); 350 if (expressionValue.equals(BigInteger.ZERO)) { 351 expressionValue = BigInteger.ONE; 352 } 353 if (basicRangeValue.equals(BigInteger.ZERO)) { 354 basicRangeValue = BigInteger.ONE; 355 } 356 currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); 357 } 358 } 359 360 /** Leaves while, do, for, if, ternary (?::), return or switch. */ 361 private void leaveConditional() { 362 final Values valuePair = popValue(); 363 final BigInteger expressionValue = valuePair.getExpressionValue(); 364 BigInteger basicRangeValue = valuePair.getRangeValue(); 365 if (currentRangeValue.equals(BigInteger.ZERO)) { 366 currentRangeValue = BigInteger.ONE; 367 } 368 if (basicRangeValue.equals(BigInteger.ZERO)) { 369 basicRangeValue = BigInteger.ONE; 370 } 371 currentRangeValue = currentRangeValue.add(expressionValue).multiply(basicRangeValue); 372 } 373 374 /** Leaves else, default or case group tokens. */ 375 private void leaveBranch() { 376 final Values valuePair = popValue(); 377 final BigInteger basicRangeValue = valuePair.getRangeValue(); 378 final BigInteger expressionValue = valuePair.getExpressionValue(); 379 if (branchVisited && currentRangeValue.equals(BigInteger.ZERO)) { 380 currentRangeValue = BigInteger.ONE; 381 } 382 currentRangeValue = currentRangeValue.subtract(BigInteger.ONE) 383 .add(basicRangeValue) 384 .add(expressionValue); 385 } 386 387 /** 388 * Process the end of a method definition. 389 * @param ast the token type representing the method definition 390 */ 391 private void leaveMethodDef(DetailAST ast) { 392 final BigInteger bigIntegerMax = BigInteger.valueOf(max); 393 if (currentRangeValue.compareTo(bigIntegerMax) > 0) { 394 log(ast, MSG_KEY, currentRangeValue, bigIntegerMax); 395 } 396 popValue(); 397 currentRangeValue = INITIAL_VALUE; 398 } 399 400 /** Leaves catch. */ 401 private void leaveAddingConditional() { 402 currentRangeValue = currentRangeValue.add(popValue().getRangeValue().add(BigInteger.ONE)); 403 } 404 405 /** 406 * Pushes the current range value on the range value stack. Pushes this token expression value 407 * on the expression value stack. 408 * @param expressionValue value of expression calculated for current token. 409 */ 410 private void pushValue(Integer expressionValue) { 411 rangeValues.push(currentRangeValue); 412 expressionValues.push(expressionValue); 413 currentRangeValue = INITIAL_VALUE; 414 } 415 416 /** 417 * Pops values from both stack of expression values and stack of range values. 418 * @return pair of head values from both of the stacks. 419 */ 420 private Values popValue() { 421 final int expressionValue = expressionValues.pop(); 422 return new Values(rangeValues.pop(), BigInteger.valueOf(expressionValue)); 423 } 424 425 /** Leaves try. */ 426 private void leaveMultiplyingConditional() { 427 currentRangeValue = currentRangeValue.add(BigInteger.ONE) 428 .multiply(popValue().getRangeValue().add(BigInteger.ONE)); 429 } 430 431 /** 432 * Calculates number of conditional operators, including inline ternary operator, for a token. 433 * @param ast inspected token. 434 * @return number of conditional operators. 435 * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.23"> 436 * Java Language Specification, §15.23</a> 437 * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.24"> 438 * Java Language Specification, §15.24</a> 439 * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25"> 440 * Java Language Specification, §15.25</a> 441 */ 442 private static int countConditionalOperators(DetailAST ast) { 443 int number = 0; 444 for (DetailAST child = ast.getFirstChild(); child != null; 445 child = child.getNextSibling()) { 446 final int type = child.getType(); 447 if (type == TokenTypes.LOR || type == TokenTypes.LAND) { 448 number++; 449 } 450 else if (type == TokenTypes.QUESTION) { 451 number += 2; 452 } 453 number += countConditionalOperators(child); 454 } 455 return number; 456 } 457 458 /** 459 * Finds a leaf, which is the most distant from the root. 460 * @param ast the root of tree. 461 * @return the leaf. 462 */ 463 private static DetailAST getLastToken(DetailAST ast) { 464 final DetailAST lastChild = ast.getLastChild(); 465 final DetailAST result; 466 if (lastChild.getFirstChild() == null) { 467 result = lastChild; 468 } 469 else { 470 result = getLastToken(lastChild); 471 } 472 return result; 473 } 474 475 /** 476 * Counts number of case tokens subject to a case group token. 477 * @param ast case group token. 478 * @return number of case tokens. 479 */ 480 private static int countCaseTokens(DetailAST ast) { 481 int counter = 0; 482 for (DetailAST iterator = ast.getFirstChild(); iterator != null; 483 iterator = iterator.getNextSibling()) { 484 if (iterator.getType() == TokenTypes.LITERAL_CASE) { 485 counter++; 486 } 487 } 488 return counter; 489 } 490 491 /** 492 * Coordinates of token end. Used to prevent inline ternary 493 * operator from being processed twice. 494 */ 495 private static class TokenEnd { 496 497 /** End line of token. */ 498 private int endLineNo; 499 500 /** End column of token. */ 501 private int endColumnNo; 502 503 /** 504 * Sets end coordinates from given token. 505 * @param endToken token. 506 */ 507 public void setToken(DetailAST endToken) { 508 if (!isAfter(endToken)) { 509 endLineNo = endToken.getLineNo(); 510 endColumnNo = endToken.getColumnNo(); 511 } 512 } 513 514 /** Sets end token coordinates to the start of the file. */ 515 public void reset() { 516 endLineNo = 0; 517 endColumnNo = 0; 518 } 519 520 /** 521 * Checks if saved coordinates located after given token. 522 * @param ast given token. 523 * @return true, if saved coordinates located after given token. 524 */ 525 public boolean isAfter(DetailAST ast) { 526 final int lineNo = ast.getLineNo(); 527 final int columnNo = ast.getColumnNo(); 528 boolean isAfter = true; 529 if (lineNo > endLineNo 530 || lineNo == endLineNo 531 && columnNo > endColumnNo) { 532 isAfter = false; 533 } 534 return isAfter; 535 } 536 537 } 538 539 /** 540 * Class that store range value and expression value. 541 */ 542 private static class Values { 543 544 /** NP value for range. */ 545 private final BigInteger rangeValue; 546 547 /** NP value for expression. */ 548 private final BigInteger expressionValue; 549 550 /** 551 * Constructor that assigns all of class fields. 552 * @param valueOfRange NP value for range 553 * @param valueOfExpression NP value for expression 554 */ 555 /* package */ Values(BigInteger valueOfRange, BigInteger valueOfExpression) { 556 rangeValue = valueOfRange; 557 expressionValue = valueOfExpression; 558 } 559 560 /** 561 * Returns NP value for range. 562 * @return NP value for range 563 */ 564 public BigInteger getRangeValue() { 565 return rangeValue; 566 } 567 568 /** 569 * Returns NP value for expression. 570 * @return NP value for expression 571 */ 572 public BigInteger getExpressionValue() { 573 return expressionValue; 574 } 575 576 } 577 578}