Skip to content

Ruby: CFG: make all expressions "post-order" nodes #7394

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

Merged
merged 13 commits into from
Dec 21, 2021

Conversation

aibaars
Copy link
Contributor

@aibaars aibaars commented Dec 14, 2021

No description provided.

@aibaars aibaars requested a review from a team as a code owner December 14, 2021 15:53
@github-actions github-actions bot added the Ruby label Dec 14, 2021
@aibaars aibaars force-pushed the ruby-cfg-expr-post branch 2 times, most recently from 4388f19 to fae3005 Compare December 15, 2021 17:35
Copy link
Contributor

@nickrolfe nickrolfe left a comment

Choose a reason for hiding this comment

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

I haven't reviewed the test output, but the changes in ControlFlowGraphImpl.qll all look sensible.

Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

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

Looks great, it even simplified some existing logic. I suggest we rebase this once #7393 has been merged, and then get rid of the last commit.

/**
* All `Expr` nodes are `PostOrderTree`s
*/
query predicate nonPostOrderExprTypes(string cls) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you consider

import codeql.ruby.controlflow.internal.Completion
import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared

query predicate nonPostOrderExpr(Expr e, string cls) {
  cls = e.getPrimaryQlClasses() and
  not exists(e.getDesugared()) and
  exists(AstNode last, Completion c |
    last(e, last, c) and
    last != e and
    c instanceof NormalCompletion
  )
}

instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

That made me realize that the modelling of InClause is actually not like (I think) it should be. There is a subtle difference: before, the node for the InClause itself was visited regardless of a match, but now it is only visited if there is a match.

There are two options: Revert 1343ed5 or use CFG splitting to make sure that InClauses are truly post-order. I don't think splitting is worth the effort, so I suggest a revert.

With a revert, we can update the check above to

query predicate nonPostOrderExpr(Expr e, string cls) {
  cls = e.getPrimaryQlClasses() and
  not exists(e.getDesugared()) and
  not e instanceof InClause and
  not e instanceof BeginExpr and
  not e instanceof Namespace and
  not e instanceof Toplevel and
  exists(AstNode last, Completion c |
    last(e, last, c) and
    last != e and
    c instanceof NormalCompletion
  )
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That made me realize that the modelling of InClause is actually not like (I think) it should be. There is a subtle difference: before, the node for the InClause itself was visited regardless of a match, but now it is only visited if there is a match.

I think this is the correct behaviour; if there is no match then the InClause has no value (not even nil).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed, perhaps InClause shouldn't be an Expr in the first place.

|
next = this.getCondition()
last(this.getPattern(), pred, c) and
not c.(MatchingCompletion).getValue() = false and
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be c.(MatchingCompletion).getValue() = true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A pattern may also end with a simple completion, for example a *var never fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could write the following instead c instanceof MatchingCompletion implies c.(MatchingCompletion).getValue() = true

hvitved
hvitved previously approved these changes Dec 20, 2021
Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

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

LGTM. I have started a final DCA run; let's wait for that before we merge.

hvitved
hvitved previously approved these changes Dec 20, 2021
@aibaars aibaars merged commit a7aff11 into github:main Dec 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants