UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

291 lines 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ControlFlowGraph = exports.CfgVertexType = void 0; exports.edgeTypeToString = edgeTypeToString; exports.equalVertex = equalVertex; exports.emptyControlFlowInformation = emptyControlFlowInformation; const assert_1 = require("../util/assert"); var CfgVertexType; (function (CfgVertexType) { /** Marks a break point in a construct (e.g., between the name and the value of an argument, or the formals and the body of a function) */ CfgVertexType["MidMarker"] = "mid"; /** The explicit exit-nodes to ensure the hammock property */ CfgVertexType["EndMarker"] = "end"; /** something like an if, assignment, ... even though in the classical sense of R they are still expressions */ CfgVertexType["Statement"] = "stm"; /** something like an addition, ... */ CfgVertexType["Expression"] = "expr"; /** a (as far as R allows this) 'basic' block */ CfgVertexType["Block"] = "blk"; })(CfgVertexType || (exports.CfgVertexType = CfgVertexType = {})); function edgeTypeToString(type) { switch (type) { case 0 /* CfgEdgeType.Fd */: return 'FD'; case 1 /* CfgEdgeType.Cd */: return 'CD'; default: throw new Error(`Unknown edge type ${JSON.stringify(type)}`); } } function equalVertex(a, b) { if (a.type !== b.type || a.id !== b.id) { return false; } else if (a.type === CfgVertexType.Block && b.type === CfgVertexType.Block) { return a.elems.length === b.elems.length && a.elems.every((e, i) => e.id === b.elems[i].id); } else if (a.type === CfgVertexType.MidMarker && b.type === CfgVertexType.MidMarker) { return a.kind === b.kind && a.root === b.root; } else if (a.type === CfgVertexType.EndMarker && b.type === CfgVertexType.EndMarker) { return a.root === b.root; } return true; } /** * This class represents the control flow graph of an R program. * The control flow may be hierarchical when confronted with function definitions (see {@link CfgSimpleVertex} and {@link CFG#rootVertexIds|rootVertexIds()}). * * There are two very simple visitors to traverse a CFG: * - {@link visitCfgInOrder} visits the graph in the order of the vertices * - {@link visitCfgInReverseOrder} visits the graph in reverse order * * If you want to prohibit modification, please refer to the {@link ReadOnlyControlFlowGraph} interface. */ class ControlFlowGraph { rootVertices = new Set(); /** Nesting-Independent vertex information, mapping the id to the vertex */ vertexInformation = new Map(); /** the basic block children map contains a mapping of ids to all vertices that are nested in basic blocks, mapping them to the Id of the block they appear in */ bbChildren = new Map(); /** basic block agnostic edges */ edgeInformation = new Map(); /** used as an optimization to avoid unnecessary lookups */ _mayHaveBasicBlocks = false; /** * Add a new vertex to the control flow graph. * * @see {@link ControlFlowGraph#addEdge|addEdge()} - to add an edge */ addVertex(vertex, rootVertex = true) { (0, assert_1.guard)(!this.vertexInformation.has(vertex.id), `Node with id ${vertex.id} already exists`); if (vertex.type === CfgVertexType.Block) { this._mayHaveBasicBlocks = true; if (vertex.elems.some(e => this.bbChildren.has(e.id) || this.rootVertices.has(e.id))) { throw new Error(`Vertex ${vertex.id} contains vertices that are already part of the graph`); } for (const elem of vertex.elems) { this.bbChildren.set(elem.id, vertex.id); } } this.vertexInformation.set(vertex.id, vertex); if (rootVertex) { this.rootVertices.add(vertex.id); } return this; } /** * Add a new edge to the control flow graph. * * @see {@link ControlFlowGraph#addVertex|addVertex()} - to add vertices * @see {@link ControlFlowGraph#addEdges|addEdges()} - to add multiple edges at once */ addEdge(from, to, edge) { const edgesFrom = this.edgeInformation.get(from) ?? new Map(); if (!this.edgeInformation.has(from)) { this.edgeInformation.set(from, edgesFrom); } edgesFrom.set(to, edge); return this; } /** * Add multiple edges from a given source vertex to the control flow graph. */ addEdges(from, to) { const edgesFrom = this.edgeInformation.get(from) ?? new Map(); if (!this.edgeInformation.has(from)) { this.edgeInformation.set(from, edgesFrom); } for (const [toId, edge] of to) { edgesFrom.set(toId, edge); } return this; } outgoingEdges(node) { return this.edgeInformation.get(node); } ingoingEdges(id) { const edges = new Map(); for (const [source, outgoing] of this.edgeInformation.entries()) { if (outgoing.has(id)) { edges.set(source, outgoing.get(id)); } } return edges; } rootIds() { return this.rootVertices; } vertices(includeBasicBlockElements = true) { if (includeBasicBlockElements) { const all = new Map(this.vertexInformation); for (const [id, block] of this.bbChildren.entries()) { const blockVertex = all.get(block); if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) { continue; } const elem = blockVertex.elems.find(e => e.id === id); if (elem !== undefined) { all.set(id, elem); } } return all; } else { return this.vertexInformation; } } getBasicBlock(elemId) { const block = this.bbChildren.get(elemId); if (block === undefined) { return undefined; } const blockVertex = this.vertexInformation.get(block); if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) { return undefined; } return blockVertex; } edges() { return this.edgeInformation; } /** * Retrieve a vertex by its id. */ getVertex(id, includeBlocks = true) { const res = this.vertexInformation.get(id); if (res || !includeBlocks) { return res; } const block = this.bbChildren.get(id); if (block === undefined) { return undefined; } const blockVertex = this.vertexInformation.get(block); if (blockVertex === undefined || blockVertex.type !== CfgVertexType.Block) { return undefined; } blockVertex.elems.find(e => e.id === id); } hasVertex(id, includeBlocks = true) { return this.vertexInformation.has(id) || (this._mayHaveBasicBlocks && includeBlocks && this.bbChildren.has(id)); } mayHaveBasicBlocks() { return this._mayHaveBasicBlocks; } /** * This removes the vertex and all edges to and from it. * @param id - the id of the vertex to remove * * @see {@link ControlFlowGraph#addVertex|addVertex()} - to add a vertex * @see {@link ControlFlowGraph#removeEdge|removeEdge()} - to remove a specific edge */ removeVertex(id) { this.vertexInformation.delete(id); this.edgeInformation.delete(id); this.bbChildren.delete(id); // remove all bbChildren with id as target for (const [a, b] of this.bbChildren.entries()) { if (b === id) { this.bbChildren.delete(a); } } for (const edges of this.edgeInformation.values()) { edges.delete(id); } this.rootVertices.delete(id); return this; } /** * Removes a all direct edges between `from` and `to` from the control flow graph. * * @see {@link ControlFlowGraph#addEdge|addEdge()} - to add an edge * @see {@link ControlFlowGraph#removeVertex|removeVertex()} - to remove a vertex and all its edges */ removeEdge(from, to) { const edges = this.edgeInformation.get(from); if (edges) { edges.delete(to); if (edges.size === 0) { this.edgeInformation.delete(from); } } return this; } /** merges b into a */ mergeTwoBasicBlocks(a, b) { const aVertex = this.getVertex(a); const bVertex = this.getVertex(b); if (!aVertex || !bVertex || aVertex.type !== CfgVertexType.Block || bVertex.type !== CfgVertexType.Block) { return this; } const bElems = bVertex.elems; aVertex.elems = aVertex.elems.concat(bElems); // update cache for (const elem of bElems) { this.bbChildren.set(elem.id, a); } // drop all edges from a to b this.removeEdge(a, b); const bOutgoing = this.outgoingEdges(b); this.removeVertex(b); // reroute all edge from b to a for (const [to, edge] of bOutgoing ?? []) { this.addEdge(a, to, edge); } return this; } /** * Merge another control flow graph into this one. * @param other - the other control flow graph to merge into this one * @param forceNested - should the other graph be assumed to be fully nested (e.g., within a function definition). * * This is the pendant of {@link DataflowGraph#mergeWith|mergeWith()} on a {@link DataflowGraph}. */ mergeWith(other, forceNested = false) { this._mayHaveBasicBlocks ||= other._mayHaveBasicBlocks; const roots = other.rootVertices; if (this._mayHaveBasicBlocks) { for (const [id, node] of other.vertexInformation) { this.addVertex(node, forceNested ? false : roots.has(id)); } } else { for (const [id, node] of other.vertexInformation) { this.vertexInformation.set(id, node); } if (!forceNested) { for (const root of roots) { this.rootVertices.add(root); } } } for (const [from, edges] of other.edgeInformation) { this.addEdges(from, edges); } return this; } } exports.ControlFlowGraph = ControlFlowGraph; function emptyControlFlowInformation() { return { returns: [], breaks: [], nexts: [], entryPoints: [], exitPoints: [], graph: new ControlFlowGraph() }; } //# sourceMappingURL=control-flow-graph.js.map