Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #11720: Kill surviving mutation in VariableDeclarationUsageDistanceCheck in getFirstNodeInsideIfBlock #11972

Conversation

Vyom-Yadav
Copy link
Member

@Vyom-Yadav Vyom-Yadav commented Jul 23, 2022

Closes #11720

Check documentation: https://checkstyle.sourceforge.io/config_coding.html#VariableDeclarationUsageDistance

Diff Reports (Contains diff, Issue for NPE at #11973):

Rationale

There is no need to loop and check all the statements in the if-else chain at once, just check the current statement, the next statement (if present), and the outer method i.e. calculateDistanceBetweenScopes will iterate over the remaining branches, we just add the head of the remaining branches to variableUsageExpressions for it to be analyzed in the next iteration.

Also support for if statements without {} was added, it rationale is present below.
Previous Logic:

private static DetailAST getFirstNodeInsideIfBlock(
        DetailAST block, DetailAST variable) {
    DetailAST firstNodeInsideBlock = null;


    if (!isVariableInOperatorExpr(block, variable)) {
        DetailAST currentNode = block.getLastChild();
        final List<DetailAST> variableUsageExpressions =
                new ArrayList<>();


        while (currentNode != null
                && currentNode.getType() == TokenTypes.LITERAL_ELSE) {
            final DetailAST previousNode =
                    currentNode.getPreviousSibling();


            // Checking variable usage inside IF block.
            if (isChild(previousNode, variable)) {
                variableUsageExpressions.add(previousNode);
            }


            // Looking into ELSE block, get its first child and analyze it.
            currentNode = currentNode.getFirstChild();


            if (currentNode.getType() == TokenTypes.LITERAL_IF) {
                currentNode = currentNode.getLastChild();
            }
            else if (isChild(currentNode, variable)) {
                variableUsageExpressions.add(currentNode);
                currentNode = null;
            }
        }


        // If IF block doesn't include ELSE then analyze variable usage
        // only inside IF block.
        if (currentNode != null
                && isChild(currentNode, variable)) {
            variableUsageExpressions.add(currentNode);
        }


        // If variable usage exists in several related blocks, then
        // firstNodeInsideBlock = null, otherwise if variable usage exists
        // only inside one block, then get node from
        // variableUsageExpressions.
        if (variableUsageExpressions.size() == 1) {
            firstNodeInsideBlock = variableUsageExpressions.get(0);
        }
    }


    return firstNodeInsideBlock;
}

If we see the initial logic, we get the if statements last-child, for if statements without {}, it will be:

if (true)    
    method();

AST-

LITERAL_IF -> if 
   |--LPAREN -> ( 
   |--EXPR -> EXPR 
   |   `--LITERAL_TRUE -> true 
   |--RPAREN -> ) 
   |--EXPR -> EXPR 
   |   `--METHOD_CALL -> ( 
   |       |--IDENT -> method 
   |       |--ELIST -> ELIST 
   |       `--RPAREN -> ) 
   `--SEMI -> ; 

Now the last child is SEMI and the according to the logic, it was supposed to be if statements body.
The logic was built for if statements with {} where the last child is SLIST i.e. if statement's body.

LITERAL_IF -> if 
  |--LPAREN -> ( 
  |--EXPR -> EXPR 
  |   `--LITERAL_TRUE -> true 
  |--RPAREN -> ) 
  `--SLIST -> { 
      |--EXPR -> EXPR 
      |   `--METHOD_CALL -> ( 
      |       |--IDENT -> method 
      |       |--ELIST -> ELIST 
      |       `--RPAREN -> ) 
      |--SEMI -> ; 
      `--RCURLY -> } 

Cases like this in combination with else were missing, support for that was added.


Generating reports again:

Diff Regression config: https://gist.githubusercontent.com/Vyom-Yadav/98dceb63a79f4833e85fff9b2e1464a6/raw/a06fa3c8f525c4466a67c1ee057d4ed843db2301/my_checks.xml
Report label: validateBetweenScopesTrue

@Vyom-Yadav
Copy link
Member Author

Github, generate report

@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch from 143b6e0 to 78d2c37 Compare July 23, 2022 18:30
@Vyom-Yadav
Copy link
Member Author

Github, generate report

@github-actions
Copy link
Contributor

validateBetweenScopesFalse: https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/143b6e0_2022191919/reports/diff/index.html

@github-actions
Copy link
Contributor

@Vyom-Yadav
Copy link
Member Author

Github, generate report

@github-actions
Copy link
Contributor

Copy link
Member

@nrmancuso nrmancuso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vyom-Yadav are we able to cleanly separate changes here into two PRs?

@nrmancuso nrmancuso self-assigned this Jul 26, 2022
@Vyom-Yadav
Copy link
Member Author

@Vyom-Yadav are we able to cleanly separate changes here into two PRs?

We can, but it is not recommended, the if conditions are related to each other, just removing one branch at a time can be done but it doesn't make any sense to do so, having complete correct logic that is related to each other in one PR is better IMO, but still if needed, I can split this into two :)

@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch 6 times, most recently from 86b7ae3 to 3fe931a Compare July 29, 2022 11:26
@Vyom-Yadav
Copy link
Member Author

Github, generate report

@github-actions
Copy link
Contributor

@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch from 3fe931a to 510a266 Compare July 29, 2022 15:53
@nrmancuso
Copy link
Member

@Vyom-Yadav why such a large discrepancy between reports?

@Vyom-Yadav
Copy link
Member Author

@Vyom-Yadav why such a large discrepancy between reports?

I added support for if statements without { }, that's why there is this difference, in many cases, the distance is increased by one as earlier if statements without { } were not supported.

Copy link
Member

@nrmancuso nrmancuso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item:

@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch 4 times, most recently from cb2d9ce to e998b83 Compare August 2, 2022 16:17
Copy link
Member

@nrmancuso nrmancuso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item:

@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch from e998b83 to 4bf25e9 Compare August 10, 2022 06:37
@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch from 4bf25e9 to fd21687 Compare August 15, 2022 12:16
@nrmancuso nrmancuso assigned baratali and unassigned nrmancuso Aug 16, 2022
}
final Optional<DetailAST> slistToken = TokenUtil
.findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST);
final DetailAST currentNode = block.getLastChild();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please rename it to lastNode (since there is no loop anymore and it has only one value)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

c++;
if (true)
d++;
}
Copy link
Contributor

@baratali baratali Aug 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When return is the first statement in if the last child is not SEMI:

if (true) return a;
LITERAL_IF -> if [3:8]
|--LPAREN -> ( [3:11]
|--EXPR -> EXPR [3:12]
|  `--LITERAL_TRUE -> true [3:12]
|--RPAREN -> ) [3:16]
`--LITERAL_RETURN -> return [3:18]
    |--EXPR -> EXPR [3:25]
    |  `--IDENT -> a [3:25]
    `--SEMI -> ; [3:26]

And it doesn't catch a violation in such code:

    String testReturn() {
        int a = 1; // violation
        System.out.println();
        System.out.println();
        System.out.println();
        if (true)
            return String.valueOf(a);

        return null;
    }

Can you please add it to the inputs too and test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it, it won't give a violation, the if statement evaluations works correctly, but this is considered by check logic as an initialization sequence
(isInitializationSequence = true), so it does not give a violation finally at:

if (dist > allowedDistance
&& !isInitializationSequence(variableUsageAst, variable.getText())) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a similar example as proof of working on current changes.

…nUsageDistanceCheck in getFirstNodeInsideIfBlock
@Vyom-Yadav Vyom-Yadav force-pushed the killSurvivingMutationInVariableDeclarationUsageDistanceCheck-8 branch from fd21687 to a1fa6a8 Compare August 20, 2022 09:55
@baratali baratali merged commit a97c3e8 into checkstyle:master Aug 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pitest: Kill all surviving mutations
4 participants