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

Control flow graph implementation #2687

Merged
merged 1 commit into from
Aug 15, 2018
Merged

Conversation

artemmukhin
Copy link
Member

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

@sirgl
Copy link
Contributor

sirgl commented Jul 19, 2018

It would be nice to have tests, that covers Rust control flow construction. You can see example for java here and test data here
Probably you have to linearise cfg to do it, but I think correctness definitely worth it.
Also, as I understand, you want to attach some attributes to cfg in future, so debugging borrow checker and analysis will be simpler with good testing infrastructure (I mean, these attributes should be visible in test result too).

@artemmukhin
Copy link
Member Author

@sirgl Good idea! What do you think about tests based on depth-first traversal traces?

@sirgl
Copy link
Contributor

sirgl commented Jul 23, 2018

Yes, I think, that any stable traversal is enough.
It is rather hard to understand test output (especially with non direct control flow), so I would add indices to nodes and explicitly mention to what exactly node control flow is transferred if it is not trivial.

}
}

class Node<N>(val data: N,
Copy link
Member

Choose a reason for hiding this comment

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

This should be formatted according to the convention.

import java.util.*

class Graph<N, E>(val nodes: MutableList<Node<N>>, val edges: MutableList<Edge<E>>) {
constructor() : this(mutableListOf(), mutableListOf())
Copy link
Member

Choose a reason for hiding this comment

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

It's better to use default arguments.

preds.pop()
assert(preds.size == oldPredsSize)

return result!!
Copy link
Member

Choose a reason for hiding this comment

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

Maybe It's better to use checkNotNull:

return checkNotNull(result) { "Optional (lazy) error message" }

This method throws IllegalStateException instead of NullPointerException.


private class ExitPointVisitor(
private val sink: (ExitPoint) -> Unit
) : RsVisitor() {
Copy link
Member

Choose a reason for hiding this comment

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

IMO it's better to rename all o inside visit methods to more understandable names.

val funcExit = process(func, pred)
return straightLine(callExpr, funcExit, args)
}

Copy link
Member

Choose a reason for hiding this comment

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

Why there is some space? If you want to separate some functions visually, I guess you better to leave a comment about it

}

fun depthFirstTraversalTrace(): String =
graph.depthFirstTraversal(this.entry).map { it.data.text }.joinToString("\n")
Copy link
Member

Choose a reason for hiding this comment

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

I guess you should use graph.depthFirstTraversal(this.entry).joinToString("\n") { it.data.text }

}

val nodesWithEntry = listOf(entryNode) + nodes
nodesWithEntry.forEach {
Copy link
Member

Choose a reason for hiding this comment

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

We should use simple for-loops when we can, as it is stated here

Direction.INCOMING -> incomingEdges(node)
}

fun forEachNode(f: (Node<N, E>) -> Unit) =
Copy link
Member

Choose a reason for hiding this comment

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

Those assignments are little misleading. Maybe you should convert those to bodies?

val block = exprStmt.parent as? RsBlock ?: return
if (!(block.expr == null && block.stmtList.lastOrNull() == exprStmt)) return
val parent = block.parent
if ((parent is RsFunction || parent is RsExpr && parent.isInTailPosition) && exprStmt.expr.type != TyNever) {
Copy link
Member

Choose a reason for hiding this comment

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

This condition seems heavy; maybe we should give it some name?

override fun visitExprStmt(exprStmt: RsExprStmt) {
exprStmt.acceptChildren(this)
val block = exprStmt.parent as? RsBlock ?: return
if (!(block.expr == null && block.stmtList.lastOrNull() == exprStmt)) return
Copy link
Member

Choose a reason for hiding this comment

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

Probably this will be better looking like this:

if (block.expr != null) return        
if (block.stmtList.lastOrNull() != exprStmt) return

class TailStatement(val stmt: RsExprStmt) : ExitPoint()

companion object {
fun process(fn: RsFunction, sink: (ExitPoint) -> Unit) = fn.block?.acceptChildren(ExitPointVisitor(sink))
Copy link
Member

Choose a reason for hiding this comment

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

I think you should use ordinary body functions instead of this. You don't really need process to return Unit?

* Creates graph description written in the DOT language.
* Usage: copy the output into `cfg.dot` file and run `dot -Tpng cfg.dot -o cfg.png`
*/
fun createDotDescription(): String {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe use buildString:

    /**
     * Creates graph description written in the DOT language.
     * Usage: copy the output into `cfg.dot` file and run `dot -Tpng cfg.dot -o cfg.png`
     */
    fun createDotDescription(): String =
        buildString {
            append("digraph {\n")

            graph.forEachEdge { edge ->
                val source = edge.source
                val target = edge.target
                val sourceNode = source.data
                val targetNode = target.data

                append("    \"${source.index}: ${sourceNode.text}\" -> \"${target.index}: ${targetNode.text}\";\n")
            }

            append("}\n")
        }

}
}

class CFGEdgeData(val exitingScopes: MutableList<RsElement>)
Copy link
Member

Choose a reason for hiding this comment

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

Why exitingScopes is mutable? It doesn't have usages in the project (that means, at least, it isn't tested)


val cfgBuilder = CFGBuilder(graph, entry, fnExit)
val bodyExit = cfgBuilder.process(body, entry)
cfgBuilder.addContainedEdge(bodyExit, fnExit)
Copy link
Member

Choose a reason for hiding this comment

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

I don't like such a complex logic inside constructors and prefer constructors that only assign the fields (like rust's struct literals). Maybe we can move the logic to some factory method (like ControlFlowGraph.buildFor())

}

fun addContainedEdge(source: CFGNode, target: CFGNode) {
val data = CFGEdgeData(mutableListOf())
Copy link
Member

Choose a reason for hiding this comment

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

Are we really want a mutable list here? Maybe emptyList()?

for (node in nodesWithEntry) {
pushNode(node)
while (stack.isNotEmpty()) {
val (node, iter) = stack.pop()
Copy link
Member

Choose a reason for hiding this comment

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

Name shadowing (compiler warning)

private fun addUnreachableNode(): CFGNode =
addNode(CFGNodeData.Unreachable, emptyList())

private fun addNode(data: CFGNodeData, preds: List<CFGNode>): CFGNode {
Copy link
Member

Choose a reason for hiding this comment

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

I found a lot of invocations of these methods with listOf()/emptyList() last argument. So maybe it's better to use vararg here?

private fun finishWithAstNode(element: RsElement, pred: CFGNode) =
finishWith { addAstNode(element, listOf(pred)) }

private fun withLoopScope(loopScope: LoopScope, callable: () -> Unit) {
Copy link
Member

Choose a reason for hiding this comment

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

This is enough small method that accepts a closure. I think it's an obvious candidate to be inline method.

preds.pop()
assert(preds.size == oldPredsSize)

return checkNotNull(result) { "Processing ended inconclusively" }
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we should set result to null after this. Otherwise, we can miss a bug and return a previous result.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now result sets to null before element.accept(this).

import org.rust.lang.core.psi.RsBlock
import org.rust.lang.core.psi.ext.descendantsOfType

class RsControlFlowGraphTest : RsTestBase() {
Copy link
Member

Choose a reason for hiding this comment

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

There are very few tests. Especially, there are no tests for loop expression and loop expression with breaks. I.e. loop expr without breaks is really an infinite loop and should be the last node in CFG.

Try to run these tests with coverage and ensure all methods in CFGBuilder are executed

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've added more tests.


withLoopScope(loopScope) {
val bodyExit = process(forExpr.block, loopBack)
addContainedEdge(bodyExit, loopBack)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we processing for expression just like it is loop expression, that is incorrect.

override fun visitTryExpr(tryExpr: RsTryExpr) {
val exprExit = addAstNode(tryExpr, emptyList())
val checkExpr = addDummyNode(listOf(pred))
val expr = addAstNode(tryExpr.expr, listOf(checkExpr))
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we have to process that expr too? Also, add some tests for try expr


typealias CFGGraph = Graph<CFGNodeData, CFGEdgeData>
typealias CFGNode = Node<CFGNodeData, CFGEdgeData>
typealias CFGEdge = Edge<CFGNodeData, CFGEdgeData>
Copy link
Member

Choose a reason for hiding this comment

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

CFGGraph & CFGEdge are not used anywhere

Copy link
Member Author

Choose a reason for hiding this comment

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

Got rid of CFGGraph. CFGEdge will be used in data-flow analysis.

}

override fun visitPatBinding(patBinding: RsPatBinding) =
finishWithAstNode(patBinding, pred)
Copy link
Member

@vlad20012 vlad20012 Aug 13, 2018

Choose a reason for hiding this comment

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

Looks like this method is never executed. We process RsPatBinding in visitPatIdent method (b/c RsPatBinding is a child of RsPatIdent)

Copy link
Member

Choose a reason for hiding this comment

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

Not really. It is used in the case of RsPatField. But it's enough weird. Maybe it's better to override visitPatField? and also please add a test for the "PatField" case.

@vlad20012
Copy link
Member

bors r+

bors bot added a commit that referenced this pull request Aug 15, 2018
2687: Control flow graph implementation r=vlad20012 a=ortem

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

Co-authored-by: ortem <ortem00@gmail.com>
@bors
Copy link
Contributor

bors bot commented Aug 15, 2018

Build failed

@mchernyavsky
Copy link
Member

bors retry

bors bot added a commit that referenced this pull request Aug 15, 2018
2687: Control flow graph implementation r=vlad20012 a=ortem

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

Co-authored-by: ortem <ortem00@gmail.com>
@bors
Copy link
Contributor

bors bot commented Aug 15, 2018

Build failed

@vlad20012
Copy link
Member

bors retry

@Undin
Copy link
Member

Undin commented Aug 15, 2018

bors r=vlad20012

bors bot added a commit that referenced this pull request Aug 15, 2018
2687: Control flow graph implementation r=vlad20012 a=ortem

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

Co-authored-by: ortem <ortem00@gmail.com>
@bors
Copy link
Contributor

bors bot commented Aug 15, 2018

Build failed

@Undin
Copy link
Member

Undin commented Aug 15, 2018

bors r=vlad20012

bors bot added a commit that referenced this pull request Aug 15, 2018
2687: Control flow graph implementation r=vlad20012 a=ortem

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

Co-authored-by: ortem <ortem00@gmail.com>
@bors
Copy link
Contributor

bors bot commented Aug 15, 2018

Build failed

@vlad20012
Copy link
Member

bors retry

bors bot added a commit that referenced this pull request Aug 15, 2018
2687: Control flow graph implementation r=vlad20012 a=ortem

Rust control flow graph implementation. Will be used for future data flow analysis and borrow checker.

Co-authored-by: ortem <ortem00@gmail.com>
@bors
Copy link
Contributor

bors bot commented Aug 15, 2018

@bors bors bot merged commit 48113e6 into intellij-rust:master Aug 15, 2018
@artemmukhin artemmukhin deleted the cfg branch August 16, 2018 09:53
@farodin91 farodin91 mentioned this pull request Sep 4, 2020
11 tasks
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.

None yet

6 participants