Skip to content

Issue #12671: kill pitest mutations in FinalLocalVariableCheck#13814

Merged
romani merged 1 commit intocheckstyle:masterfrom
rdiachenko:issue-12671-rd
Oct 20, 2023
Merged

Issue #12671: kill pitest mutations in FinalLocalVariableCheck#13814
romani merged 1 commit intocheckstyle:masterfrom
rdiachenko:issue-12671-rd

Conversation

@rdiachenko
Copy link
Copy Markdown
Member

@rdiachenko rdiachenko commented Oct 3, 2023

Issue #12671

Diff Regression config: https://gist.githubusercontent.com/Kevin222004/6e271c2ae713051c0a30111211de5627/raw/9f34341ce6c7feabbda4bc3d59fa62df427d775d/FinalLocal.xml
Diff Regression projects: https://gist.githubusercontent.com/Kevin222004/9600f179b602d4c971bdb0a050099005/raw/360a95ed7bb60d7a0956e531199d484c4d6f6617/test-projects.properties
Report label: Regression-Oct-03

Attempt to improve this PR and kill mutation without affecting performance issues mentioned in the comments.

We don't need a separate stack for prevScopeUninitializedVariables. We can track those variables within ScopeData and rely on the current check's logic to pop from scopeStack.

@rdiachenko rdiachenko self-assigned this Oct 3, 2023
@rdiachenko rdiachenko marked this pull request as ready for review October 3, 2023 11:16
@rdiachenko rdiachenko requested a review from romani October 3, 2023 11:16
@rdiachenko rdiachenko assigned romani and unassigned rdiachenko Oct 3, 2023
@rdiachenko
Copy link
Copy Markdown
Member Author

Github, generate report

@rdiachenko rdiachenko requested a review from rnveach October 3, 2023 11:26
@romani
Copy link
Copy Markdown
Member

romani commented Oct 3, 2023

Please read https://github.com/checkstyle/contribution/tree/master/checkstyle-tester#basic-difference-report-with-label-and-custom-projects-list
We need to put link to config and link to list of projects to PR description

@rdiachenko
Copy link
Copy Markdown
Member Author

Github, generate report

Copy link
Copy Markdown
Member

@romani romani left a comment

Choose a reason for hiding this comment

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

ok to merge if report has no diff

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Oct 3, 2023

@romani romani assigned rnveach and unassigned romani Oct 3, 2023
@rnveach rnveach requested a review from nrmancuso October 3, 2023 14:13
@rnveach
Copy link
Copy Markdown
Contributor

rnveach commented Oct 3, 2023

We don't need a separate stack for prevScopeUninitializedVariables. We can track those variables within ScopeData and rely on the current check's logic to pop from scopeStack.

Explain to me how this code is the same to the previous code, and there is no logical differences.

Also, does this fix #13184 (comment) ?

@rdiachenko
Copy link
Copy Markdown
Member Author

Explain to me how this code is the same to the previous code, and there is no logical differences.

The prevScopeUninitializedVariables was used for two tokens: EXPR and SLIST.

EXPR

visitToken stays unchanged. storePrevScopeUninitializedVariableData() stores the current state of uninitialized variables into scopeData.prevScopeUninitializedVariables instead of pushing it into the stack prevScopeUninitializedVariables.push(prevScopeUninitializedVariableData);.

leaveToken logically stays the same. updateAllUninitializedVariables() iterates over all scopes trying to update uninitialized variables updateUninitializedVariables(scopeData.prevScopeUninitializedVariables). If you check the old logic, it did very similar thing. It tried to update uninitialized variables from the last scope and then iterated via all scopes (including the last scope again):

private void updateAllUninitializedVariables(
             Deque<DetailAST> prevScopeUninitializedVariableData) {
...
            // Check for only previous scope
             updateUninitializedVariables(prevScopeUninitializedVariableData);
             // Check for rest of the scope
             prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables);
...
}

SLIST

visitToken stays unchanged. Uninitialized variables are stored in the curent scope and then a new scope is pushed into the stack:

storePrevScopeUninitializedVariableData();
scopeStack.push(new ScopeData());

leaveToken logically stays the same. The current scope is first popped scope = scopeStack.pop().scope; and then updateAllUninitializedVariables() iterates over all scopes trying to update uninitialized variables. Because of the ordering of operations in the visitToken the uninitialized variables are not lost here. Same was with the old logic where it explicitly popped the element from the prevScopeUninitializedVariables.

Also, does this fix #13184 (comment) ?

I haven't checked the state of the scopeStack between analysis of multiple files, similar issue may be present as with the removed prevScopeUninitializedVariables stack for EXPR branch, but I think it should be investigated and handled as a separate issue.

@rnveach
Copy link
Copy Markdown
Contributor

rnveach commented Oct 8, 2023

@rdiachenko Let me know if I missed it, but what about the fact that we are switching from a double array to a single?

This means we are no longer returning to a previous state when we would have pop. storePrevScopeUninitializedVariableData always did a push on both tokens you described, and then did a pop on leaving them. It acted as a save/store of the current state, before moving into a new, nested state.

Now with your changes, when we call updateAllUninitializedVariables we seem to be removing things in the array when shouldRemove is true. So instead of doing a "return" to a previous state, we almost clear out the array assuming we are starting fresh and lose the previous values.

Because of the ordering of operations in the visitToken the uninitialized variables are not lost here.

It seems we are, as I described above.

@rdiachenko
Copy link
Copy Markdown
Member Author

@rnveach

but what about the fact that we are switching from a double array to a single?

We are not switching from a doubly array. Instead, we are moving the second dimention into ScopeData:

// before
private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
private final Deque<Deque<DetailAST>> prevScopeUninitializedVariables = new ArrayDeque<>();

// after
private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
private static final class ScopeData {
  private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>();
}

Because we reuse scopeStack which is a stack with each element ScopeData containing an array of prevScopeUninitializedVariables, we are still in two dimentions.

This means we are no longer returning to a previous state when we would have pop.

Because we still use double array and push / pop operations are equivalent to what they used to be, we do return to the previous state. Let's check this:

EXPR

// before
public void visitToken(DetailAST ast) {
  case TokenTypes.EXPR:
    if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) {
      // store `Deque<DetailAST> prevScopeUninitializedVariableData` 
      // into `prevScopeUninitializedVariables`
      storePrevScopeUninitializedVariableData(); 
    }
}

public void leaveToken(DetailAST ast) {
  case TokenTypes.EXPR:
    if (parentAst.getType() == TokenTypes.SWITCH_RULE) {
      // no pop happens
      prevScopeUninitializedVariableData = prevScopeUninitializedVariables.peek(); 
      if (shouldUpdateUninitializedVariables(parentAst)) {
        updateAllUninitializedVariables(prevScopeUninitializedVariableData);
      }
    }
}


// after
public void visitToken(DetailAST ast) {
  case TokenTypes.EXPR:
    if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) {
      // store `Deque<DetailAST> prevScopeUninitializedVariableData` into `scopeStack`
      storePrevScopeUninitializedVariableData(); 
    }
}

public void leaveToken(DetailAST ast) {
  case TokenTypes.EXPR:
    if (parentAst.getType() == TokenTypes.SWITCH_RULE ....) {
      // no pop happens, similar iteration over all scopes trying to update
      // uninitialized variables as before
      updateAllUninitializedVariables(); 
    }
}

SLIST

// before
public void visitToken(DetailAST ast) {
  case TokenTypes.SLIST:
    if (ast.getParent().getType() != TokenTypes.CASE_GROUP....) {
      // store `Deque<DetailAST> prevScopeUninitializedVariableData` 
      // into `prevScopeUninitializedVariables `
      storePrevScopeUninitializedVariableData();
      scopeStack.push(new ScopeData());
    }
}

public void leaveToken(DetailAST ast) {
  case TokenTypes.SLIST:
    prevScopeUninitializedVariableData = prevScopeUninitializedVariables.peek();
      if (parentAst.getType() != TokenTypes.CASE_GROUP....) {
        scope = scopeStack.pop().scope;
        prevScopeUninitializedVariables.pop();
      }
      if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) {
        // use the last element from `prevScopeUninitializedVariables` which was popped
        updateAllUninitializedVariables(prevScopeUninitializedVariableData);
      }
}


// after
public void visitToken(DetailAST ast) {
  case TokenTypes.SLIST:
    if (ast.getParent().getType() != TokenTypes.CASE_GROUP....) {
      // store `Deque<DetailAST> prevScopeUninitializedVariableData` into `scopeStack`
      storePrevScopeUninitializedVariableData();
      scopeStack.push(new ScopeData());
    }
}

public void leaveToken(DetailAST ast) {
  case TokenTypes.SLIST:
    if (parentAst.getType() != TokenTypes.CASE_GROUP......) {
      // it is safe to pop because `prevScopeUninitializedVariableData` is contained
      // in the next element due to the push ordering in the `visitToken`
      scope = scopeStack.pop().scope;
    }
    if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) {
      // at this point `scopeStack.peek` contains `prevScopeUninitializedVariableData` 
      // which was stored during `visitToken`
      updateAllUninitializedVariables(); 
    }
}

Copy link
Copy Markdown
Contributor

@rnveach rnveach left a comment

Choose a reason for hiding this comment

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

I think I am good with the changes now. Enough analysis has been done that I agree with that this hopefully won't be an issue.

@rnveach rnveach assigned nrmancuso and unassigned rnveach Oct 16, 2023
@romani romani requested a review from Vyom-Yadav October 16, 2023 15:46
Copy link
Copy Markdown
Member

@Vyom-Yadav Vyom-Yadav left a comment

Choose a reason for hiding this comment

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

Doubt:

Comment on lines 277 to -286
scope = scopeStack.pop().scope;
prevScopeUninitializedVariables.pop();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

prevScopeUninitializedVariables.pop(); was only popped here. But now that prevScopeUV are stored in ScopeData, which is:

@rdiachenko Can you please confirm that this won't cause a problem?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It shouldn't cause a problem because of the equivalent push in the visitToken. The popped value always contains empty previous data list at this point.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Resolved

@nrmancuso nrmancuso assigned Vyom-Yadav and unassigned nrmancuso Oct 20, 2023
Copy link
Copy Markdown
Member

@Vyom-Yadav Vyom-Yadav left a comment

Choose a reason for hiding this comment

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

lgtm!

@Vyom-Yadav Vyom-Yadav assigned romani and unassigned Vyom-Yadav Oct 20, 2023
@romani romani merged commit fd9d5cb into checkstyle:master Oct 20, 2023
@rdiachenko rdiachenko deleted the issue-12671-rd branch October 20, 2023 18:26
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.

5 participants