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

CFG: fix wrong exit with binop and for loop #413

Merged
merged 7 commits into from
Oct 14, 2023
Merged
12 changes: 5 additions & 7 deletions src/cli/repl/commands/cfg.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { ReplCommand } from './main'
import { SteppingSlicer } from '../../../core'
import { requestFromInput, RShell } from '../../../r-bridge'
import {
cfgToMermaid, cfgToMermaidUrl
} from '../../../util/mermaid'
import { extractCFG } from '../../../util/cfg'
import { cfgToMermaid, cfgToMermaidUrl } from '../../../util/mermaid'

async function controlflow(shell: RShell, remainingLine: string) {
return await new SteppingSlicer({
Expand All @@ -15,27 +13,27 @@
}

export const controlflowCommand: ReplCommand = {
description: 'Get mermaid code for the control-flow graph of R code, start with \'file://\' to indicate a file.',
description: 'Get mermaid code for the control-flow graph of R code, start with \'file://\' to indicate a file',
usageExample: ':controlflow',
aliases: [ 'cfg' ],
script: false,
fn: async(output, shell, remainingLine) => {
const result = await controlflow(shell, remainingLine)

const cfg = extractCFG(result.normalize)
output.stdout(cfgToMermaid(cfg))
output.stdout(cfgToMermaid(cfg, result.normalize))

Check warning on line 24 in src/cli/repl/commands/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/repl/commands/cfg.ts#L24

Added line #L24 was not covered by tests
}
}

export const controlflowStarCommand: ReplCommand = {
description: 'Get a mermaid url of the control-flow graph of R code, start with \'file://\' to indicate a file.',
description: 'Get a mermaid url of the control-flow graph of R code, start with \'file://\' to indicate a file',
usageExample: ':controlflow',
aliases: [ 'cfg*' ],
script: false,
fn: async(output, shell, remainingLine) => {
const result = await controlflow(shell, remainingLine)

const cfg = extractCFG(result.normalize)
output.stdout(cfgToMermaidUrl(cfg))
output.stdout(cfgToMermaidUrl(cfg, result.normalize))

Check warning on line 37 in src/cli/repl/commands/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/cli/repl/commands/cfg.ts#L37

Added line #L37 was not covered by tests
}
}
6 changes: 3 additions & 3 deletions src/cli/repl/commands/dataflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { requestFromInput, RShell } from '../../../r-bridge'
import {
graphToMermaid,
graphToMermaidUrl
} from '../../../util/mermaid'
} from '../../../util/mermaid/dfg'

async function dataflow(shell: RShell, remainingLine: string) {
return await new SteppingSlicer({
Expand All @@ -15,7 +15,7 @@ async function dataflow(shell: RShell, remainingLine: string) {
}

export const dataflowCommand: ReplCommand = {
description: 'Get mermaid code for the dataflow graph of R code, start with \'file://\' to indicate a file.',
description: 'Get mermaid code for the dataflow graph of R code, start with \'file://\' to indicate a file',
usageExample: ':dataflow',
aliases: [ 'd', 'df' ],
script: false,
Expand All @@ -27,7 +27,7 @@ export const dataflowCommand: ReplCommand = {
}

export const dataflowStarCommand: ReplCommand = {
description: 'Get a mermaid url of the dataflow graph of R code, start with \'file://\' to indicate a file.',
description: 'Get a mermaid url of the dataflow graph of R code, start with \'file://\' to indicate a file',
usageExample: ':dataflow*',
aliases: [ 'd*', 'df*' ],
script: false,
Expand Down
4 changes: 2 additions & 2 deletions src/cli/repl/commands/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async function normalize(shell: RShell, remainingLine: string) {
}

export const normalizeCommand: ReplCommand = {
description: 'Get mermaid code for the normalized AST of R code, start with \'file://\' to indicate a file.',
description: 'Get mermaid code for the normalized AST of R code, start with \'file://\' to indicate a file',
usageExample: ':normalize',
aliases: [ 'n' ],
script: false,
Expand All @@ -24,7 +24,7 @@ export const normalizeCommand: ReplCommand = {
}

export const normalizeStarCommand: ReplCommand = {
description: 'Get a mermaid url of the normalized AST of R code, start with \'file://\' to indicate a file.',
description: 'Get a mermaid url of the normalized AST of R code, start with \'file://\' to indicate a file',
usageExample: ':normalize',
aliases: [ 'n*' ],
script: false,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/repl/commands/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function depthListToTextTree(list: Readonly<DepthList>, config: XmlParserConfig,


export const parseCommand: ReplCommand = {
description: 'Prints ASCII Art of the parsed, unmodified AST, start with \'file://\' to indicate a file.',
description: 'Prints ASCII Art of the parsed, unmodified AST, start with \'file://\' to indicate a file',
usageExample: ':parse',
aliases: [ 'p' ],
script: false,
Expand Down
8 changes: 1 addition & 7 deletions src/dataflow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,4 @@ export const dataflowLogger = log.getSubLogger({ name: 'dataflow' })
export * from './graph'
export * from './extractor'
export * from './environments/environment'
export { diffGraphsToMermaidUrl } from '../util/mermaid'
export { diffGraphsToMermaid } from '../util/mermaid'
export { LabeledDiffGraph } from '../util/mermaid'
export { graphToMermaidUrl } from '../util/mermaid'
export { mermaidCodeToUrl } from '../util/mermaid'
export { graphToMermaid } from '../util/mermaid'
export { formatRange } from '../util/mermaid'
export * from '../util/mermaid/dfg'
50 changes: 22 additions & 28 deletions src/util/cfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
export interface CfgVertex {
id: NodeId
name: string
/** the content may be undefined, if the node is an artificial exit point node that i use to mark the exit point of an if condition, a function, etc. */
content: string | undefined
/** in case of a function definition */
children?: NodeId[]
}
Expand Down Expand Up @@ -155,13 +153,10 @@
}


function getLexeme(n: RNodeWithParent) {
return n.info.fullLexeme ?? n.lexeme ?? '<unknown>'
}

function cfgLeaf(leaf: RNodeWithParent): ControlFlowInformation {
const graph = new ControlFlowGraph()
graph.addVertex({ id: leaf.info.id, name: leaf.type, content: getLexeme(leaf) })
graph.addVertex({ id: leaf.info.id, name: leaf.type })
return { graph, breaks: [], nexts: [], returns: [], exitPoints: [leaf.info.id], entryPoints: [leaf.info.id] }
}

Expand All @@ -179,8 +174,8 @@

function cfgIfThenElse(ifNode: RNodeWithParent, condition: ControlFlowInformation, then: ControlFlowInformation, otherwise: ControlFlowInformation | undefined): ControlFlowInformation {
const graph = new ControlFlowGraph()
graph.addVertex({ id: ifNode.info.id, name: ifNode.type, content: getLexeme(ifNode) })
graph.addVertex({ id: ifNode.info.id + '-exit', name: 'if-exit', content: undefined })
graph.addVertex({ id: ifNode.info.id, name: ifNode.type })
graph.addVertex({ id: ifNode.info.id + '-exit', name: 'if-exit' })
graph.merge(condition.graph)
graph.merge(then.graph)
if(otherwise) {
Expand Down Expand Up @@ -220,8 +215,8 @@

function cfgRepeat(repeat: RRepeatLoop<ParentInformation>, body: ControlFlowInformation): ControlFlowInformation {
const graph = body.graph
graph.addVertex({ id: repeat.info.id, name: repeat.type, content: getLexeme(repeat) })
graph.addVertex({ id: repeat.info.id + '-exit', name: 'repeat-exit', content: undefined })
graph.addVertex({ id: repeat.info.id, name: repeat.type })
graph.addVertex({ id: repeat.info.id + '-exit', name: 'repeat-exit' })

Check warning on line 219 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L218-L219

Added lines #L218 - L219 were not covered by tests

for(const entryPoint of body.entryPoints) {
graph.addEdge(repeat.info.id, entryPoint, { label: 'FD' })
Expand All @@ -241,8 +236,8 @@

function cfgWhile(whileLoop: RWhileLoop<ParentInformation>, condition: ControlFlowInformation, body: ControlFlowInformation): ControlFlowInformation {
const graph = condition.graph
graph.addVertex({ id: whileLoop.info.id, name: whileLoop.type, content: getLexeme(whileLoop) })
graph.addVertex({ id: whileLoop.info.id + '-exit', name: 'while-exit', content: undefined })
graph.addVertex({ id: whileLoop.info.id, name: whileLoop.type })
graph.addVertex({ id: whileLoop.info.id + '-exit', name: 'while-exit' })

Check warning on line 240 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L239-L240

Added lines #L239 - L240 were not covered by tests

graph.merge(body.graph)

Expand Down Expand Up @@ -277,8 +272,8 @@

function cfgFor(forLoop: RForLoop<ParentInformation>, variable: ControlFlowInformation, vector: ControlFlowInformation, body: ControlFlowInformation): ControlFlowInformation {
const graph = variable.graph
graph.addVertex({ id: forLoop.info.id, name: forLoop.type, content: getLexeme(forLoop) })
graph.addVertex({ id: forLoop.info.id + '-exit', name: 'for-exit', content: undefined })
graph.addVertex({ id: forLoop.info.id, name: forLoop.type })
graph.addVertex({ id: forLoop.info.id + '-exit', name: 'for-exit' })

Check warning on line 276 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L275-L276

Added lines #L275 - L276 were not covered by tests

graph.merge(vector.graph)
graph.merge(body.graph)
Expand All @@ -299,10 +294,6 @@
}
}

for(const entryPoint of body.entryPoints) {
graph.addEdge(forLoop.info.id, entryPoint, { label: 'FD' })
}

for(const next of [...body.nexts, ...body.exitPoints]) {
graph.addEdge(forLoop.info.id, next, { label: 'FD' })
}
Expand All @@ -321,9 +312,9 @@
function cfgFunctionDefinition(fn: RFunctionDefinition<ParentInformation>, params: ControlFlowInformation[], body: ControlFlowInformation): ControlFlowInformation {
const graph = new ControlFlowGraph()
const children: NodeId[] = [fn.info.id + '-params', fn.info.id + '-exit']
graph.addVertex({ id: fn.info.id + '-params', name: 'function-parameters', content: undefined }, false)
graph.addVertex({ id: fn.info.id + '-exit', name: 'function-exit', content: undefined }, false)
graph.addVertex({ id: fn.info.id, name: fn.type, content: getLexeme(fn), children })
graph.addVertex({ id: fn.info.id + '-params', name: 'function-parameters' }, false)
graph.addVertex({ id: fn.info.id + '-exit', name: 'function-exit' }, false)
graph.addVertex({ id: fn.info.id, name: fn.type, children })

Check warning on line 317 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L315-L317

Added lines #L315 - L317 were not covered by tests

graph.merge(body.graph, true)
children.push(...body.graph.rootVertexIds())
Expand Down Expand Up @@ -352,9 +343,10 @@

function cfgBinaryOp(binOp: RNodeWithParent, lhs: ControlFlowInformation, rhs: ControlFlowInformation): ControlFlowInformation {
const graph = new ControlFlowGraph().merge(lhs.graph).merge(rhs.graph)
const result: ControlFlowInformation = { graph, breaks: [...lhs.breaks, ...rhs.breaks], nexts: [...lhs.nexts, ...rhs.nexts], returns: [...lhs.returns, ...rhs.returns], entryPoints: [binOp.info.id], exitPoints: [...rhs.entryPoints] }
const result: ControlFlowInformation = { graph, breaks: [...lhs.breaks, ...rhs.breaks], nexts: [...lhs.nexts, ...rhs.nexts], returns: [...lhs.returns, ...rhs.returns], entryPoints: [binOp.info.id], exitPoints: [binOp.info.id + '-exit'] }

graph.addVertex({ id: binOp.info.id, name: binOp.type, content: getLexeme(binOp) })
graph.addVertex({ id: binOp.info.id, name: binOp.type })
graph.addVertex({ id: binOp.info.id + '-exit', name: 'binOp-exit' })

for(const exitPoint of lhs.exitPoints) {
for(const entryPoint of rhs.entryPoints) {
Expand All @@ -364,15 +356,18 @@
for(const entryPoint of lhs.entryPoints) {
graph.addEdge(entryPoint, binOp.info.id, { label: 'FD' })
}
for(const exitPoint of rhs.exitPoints) {
graph.addEdge(binOp.info.id + '-exit', exitPoint, { label: 'FD' })
}

return result
}

function cfgAccess(access: RAccess<ParentInformation>, name: ControlFlowInformation, accessors: string | (ControlFlowInformation | null)[]): ControlFlowInformation {
const result = name
const graph = result.graph
graph.addVertex({ id: access.info.id, name: access.type, content: getLexeme(access) })
graph.addVertex({ id: access.info.id + '-exit', name: 'access-exit', content: undefined })
graph.addVertex({ id: access.info.id, name: access.type })
graph.addVertex({ id: access.info.id + '-exit', name: 'access-exit' })

Check warning on line 370 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L369-L370

Added lines #L369 - L370 were not covered by tests
for(const entry of name.entryPoints) {
graph.addEdge(entry, access.info.id, { label: 'FD' })
}
Expand Down Expand Up @@ -403,7 +398,7 @@
const graph = operand.graph
const result: ControlFlowInformation = { ...operand, graph, exitPoints: [unary.info.id] }

graph.addVertex({ id: unary.info.id, name: unary.type, content: getLexeme(unary) })
graph.addVertex({ id: unary.info.id, name: unary.type })

Check warning on line 401 in src/util/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/cfg.ts#L401

Added line #L401 was not covered by tests

return result
}
Expand Down Expand Up @@ -462,7 +457,7 @@
}
for(const [id, aInfo] of aVert) {
const bInfo = bVert.get(id)
if(bInfo === undefined || aInfo.name !== bInfo.name || aInfo.content !== bInfo.content || equalChildren(aInfo.children, bInfo.children)) {
if(bInfo === undefined || aInfo.name !== bInfo.name || equalChildren(aInfo.children, bInfo.children)) {
return false
}
}
Expand Down Expand Up @@ -501,7 +496,6 @@
.map(([id, v]) => ({
id,
name: v.name,
content: v.content,
children: v.children
})),
edges: [...cfg.graph.edges()].flatMap(([fromId, targets]) =>
Expand Down
24 changes: 24 additions & 0 deletions src/util/mermaid/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RNodeWithParent, RoleInParent, visitAst } from '../../r-bridge'
import { escapeMarkdown, mermaidCodeToUrl } from './mermaid'

export function normalizedAstToMermaid(ast: RNodeWithParent, prefix = ''): string {
let output = prefix + 'flowchart TD\n'
visitAst(ast, n => {

Check warning on line 6 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L5-L6

Added lines #L5 - L6 were not covered by tests
const name = `${n.type} (${n.info.id})\\n${n.lexeme ?? ' '}`
output += ` n${n.info.id}(["${escapeMarkdown(name)}"])\n`

Check warning on line 8 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L8

Added line #L8 was not covered by tests
if(n.info.parent !== undefined) {
const context = n.info

Check warning on line 10 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L10

Added line #L10 was not covered by tests
const roleSuffix = context.role === RoleInParent.ExpressionListChild || context.role === RoleInParent.FunctionCallArgument || context.role === RoleInParent.FunctionDefinitionParameter ? `-${context.index}` : ''
output += ` n${n.info.parent} -->|"${context.role}${roleSuffix}"| n${n.info.id}\n`

Check warning on line 12 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L12

Added line #L12 was not covered by tests
}
return false

Check warning on line 14 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L14

Added line #L14 was not covered by tests
})
return output

Check warning on line 16 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L16

Added line #L16 was not covered by tests
}

/**
* Use mermaid to visualize the normalized AST.
*/
export function normalizedAstToMermaidUrl(ast: RNodeWithParent, prefix = ''): string {
return mermaidCodeToUrl(normalizedAstToMermaid(ast, prefix))

Check warning on line 23 in src/util/mermaid/ast.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/ast.ts#L23

Added line #L23 was not covered by tests
}
38 changes: 38 additions & 0 deletions src/util/mermaid/cfg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NormalizedAst, RNodeWithParent } from '../../r-bridge'
import { ControlFlowInformation } from '../cfg'
import { escapeMarkdown, mermaidCodeToUrl } from './mermaid'

function getLexeme(n?: RNodeWithParent) {

Check warning on line 5 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L5

Added line #L5 was not covered by tests
return n ? n.info.fullLexeme ?? n.lexeme ?? '<unknown>' : ''
}


export function cfgToMermaid(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix = ''): string {
let output = prefix + 'flowchart TD\n'

Check warning on line 11 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L11

Added line #L11 was not covered by tests

for(const [id, vertex] of cfg.graph.vertices()) {
const normalizedVertex = normalizedAst.idMap.get(id)
const content = getLexeme(normalizedVertex)

Check warning on line 15 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L13-L15

Added lines #L13 - L15 were not covered by tests
if(content.length > 0) {
const name = `"\`${escapeMarkdown(vertex.name)} (${id})\n${escapeMarkdown(JSON.stringify(content))}\`"`
output += ` n${id}[${name}]\n`

Check warning on line 18 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L17-L18

Added lines #L17 - L18 were not covered by tests
} else {
output += ` n${id}(( ))\n`

Check warning on line 20 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L20

Added line #L20 was not covered by tests
}
}
for(const [from, targets] of cfg.graph.edges()) {
for(const [to, edge] of targets) {

Check warning on line 24 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L23-L24

Added lines #L23 - L24 were not covered by tests
const edgeType = edge.label === 'CD' ? '-->' : '-.->'
const edgeSuffix = edge.label === 'CD' ? ` (${edge.when})` : ''
output += ` n${from} ${edgeType}|"${escapeMarkdown(edge.label)}${edgeSuffix}"| n${to}\n`

Check warning on line 27 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L27

Added line #L27 was not covered by tests
}
}
return output

Check warning on line 30 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L30

Added line #L30 was not covered by tests
}

/**
* Use mermaid to visualize the normalized AST.
*/
export function cfgToMermaidUrl(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix = ''): string {
return mermaidCodeToUrl(cfgToMermaid(cfg, normalizedAst, prefix))

Check warning on line 37 in src/util/mermaid/cfg.ts

View check run for this annotation

Codecov / codecov/patch

src/util/mermaid/cfg.ts#L37

Added line #L37 was not covered by tests
}
Loading