UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

248 lines 9.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CallGraph = void 0; const graph_1 = require("./graph"); const vertex_1 = require("./vertex"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const linker_1 = require("../internal/linker"); const edge_1 = require("./edge"); const built_in_proc_name_1 = require("../environments/built-in-proc-name"); const defaultmap_1 = require("../../util/collections/defaultmap"); const graph_helper_1 = require("./graph-helper"); function processCds(vtx, graph, result, state) { for (const tar of vtx.cds ?? []) { const targetVtx = graph.getVertex(tar.id); if (targetVtx) { processUnknown(targetVtx, undefined, graph, result, state); } } } const UntargetedCallFollow = edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedByOnCall | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.Returns; const UntargetedCallAvoid = edge_1.EdgeType.NonStandardEvaluation | edge_1.EdgeType.Argument; /** * This tracks the known symbol origins for a function call for which we know that flowr found no targets! */ function fallbackUntargetedCall(vtx, graph) { // we track all aliases to their roots here, we know there is no known call target const collected = new Set(); const visited = new Set(); const toVisit = [vtx.id]; while (toVisit.length > 0) { const currentId = toVisit.pop(); if (visited.has(currentId)) { continue; } visited.add(currentId); const currentVtx = graph.getVertex(currentId); if (!currentVtx) { continue; } let addedNew = false; for (const [tar, e] of graph.outgoingEdges(currentId) ?? []) { if (edge_1.DfEdge.includesType(e, UntargetedCallFollow) && edge_1.DfEdge.doesNotIncludeType(e, UntargetedCallAvoid)) { addedNew = true; toVisit.push(tar); } } // we have reached our end(s) if (!addedNew && currentId !== vtx.id) { collected.add(currentId); } } return collected; } function processCall(vtx, from, graph, result, state) { const vid = vtx.id; if (from) { result.addEdge(from, vid, edge_1.EdgeType.Calls); } if (state.visited.has(vid)) { return; } state.visited.add(vid); result.addVertex(vtx, undefined, true); processCds(vtx, graph, result, state); // for each call, resolve the targets const tars = (0, linker_1.getAllFunctionCallTargets)(vid, graph, vtx.environment); let addedTarget = false; let addedBiTarget = false; for (const tar of tars) { if (node_id_1.NodeId.isBuiltIn(tar)) { result.addEdge(vid, tar, edge_1.EdgeType.Calls); addedTarget = true; addedBiTarget = true; continue; } const targetVtx = graph.getVertex(tar); if (targetVtx?.tag !== vertex_1.VertexType.FunctionDefinition) { continue; } addedTarget = true; processFunctionDefinition(targetVtx, vid, graph, result, state); } if (!addedBiTarget && vtx.origin !== 'unnamed') { for (const origs of vtx.origin) { if (origs.startsWith('builtin:')) { addedTarget = true; result.addEdge(vid, node_id_1.NodeId.toBuiltIn(origs.substring('builtin:'.length)), edge_1.EdgeType.Calls); } } } if (!addedTarget) { const origs = fallbackUntargetedCall(vtx, graph); for (const ori of origs) { const oriVtx = graph.getVertex(ori); if (!oriVtx) { continue; } result.addEdge(vid, ori, edge_1.EdgeType.Calls); const name = graph.idMap?.get(ori); if (name?.lexeme && oriVtx.tag === vertex_1.VertexType.Use) { result.addVertex({ ...oriVtx, tag: vertex_1.VertexType.FunctionCall, name: name.lexeme, onlyBuiltin: false, origin: [built_in_proc_name_1.BuiltInProcName.Function], args: [] }, oriVtx.environment); } } } // handle arguments, traversing the 'reads' and the 'returns' edges for (const [tar, e] of graph.outgoingEdges(vtx.id) ?? []) { if (edge_1.DfEdge.doesNotIncludeType(e, edge_1.EdgeType.Reads | edge_1.EdgeType.Returns | edge_1.EdgeType.Argument)) { continue; } const tVtx = graph.getVertex(tar); if (!tVtx) { continue; } processUnknown(tVtx, vtx.id, graph, result, state); } } function processUnknown(vtx, from, graph, result, state) { switch (vtx.tag) { case vertex_1.VertexType.FunctionCall: processCall(vtx, from, graph, result, state); return; case vertex_1.VertexType.FunctionDefinition: if (from) { result.addEdge(from, node_id_1.NodeId.toBuiltIn('function'), edge_1.EdgeType.Calls); } return; default: return; } } function processFunctionDefinition(vtx, from, graph, result, state) { if (from) { result.addEdge(from, vtx.id, edge_1.EdgeType.Calls); } if (state.visited.has(vtx.id)) { return; } state.visited.add(vtx.id); result.addVertex(vtx, undefined, true); processCds(vtx, graph, result, state); const exits = new Set(vtx.exitPoints); state.potentials.push([vtx.id, vtx.subflow.graph.difference(exits)]); for (const { nodeId } of exits) { const v = graph.getVertex(nodeId); if (v) { processUnknown(v, vtx.id, graph, result, state); } } } /** * Helper object for call-graphs, you can compute new call graphs based on {@link CallGraph.compute}. * @see {@link Dataflow} * @see {@link CallGraph} */ exports.CallGraph = { name: 'CallGraph', ...graph_helper_1.GraphHelper, /** * Extracts the sub call graph from the given call graph, starting from the given entry points. */ computeSubCallGraph(graph, entryPoints) { const result = new graph_1.DataflowGraph(graph.idMap); const toVisit = Array.from(entryPoints); const visited = new Set(); while (toVisit.length > 0) { const currentId = toVisit.pop(); if (visited.has(currentId)) { continue; } visited.add(currentId); const currentVtx = graph.getVertex(currentId); if (!currentVtx) { continue; } result.addVertex(currentVtx, undefined, true); for (const [tar, e] of graph.outgoingEdges(currentId) ?? []) { if (edge_1.DfEdge.includesType(e, edge_1.EdgeType.Calls)) { result.addEdge(currentId, tar, edge_1.EdgeType.Calls); toVisit.push(tar); } } } return result; }, /** * Reduces the call graph by dropping all transitive edges. */ dropTransitiveEdges(graph) { const newCg = new graph_1.DataflowGraph(graph.idMap); newCg.mergeVertices(graph); const knownReachability = new defaultmap_1.DefaultMap(() => new Set()); // heuristically sort by dif in ids const es = Array.from(graph.edges(), ([e, ts]) => ts.entries().map(([t, { types }]) => [e, t, types]).toArray()).flat() .sort((a, b) => String(a[0]).localeCompare(String(a[1])) - String(b[0]).localeCompare(String(b[1]))); for (const [from, to, types] of es) { if (!exports.CallGraph.reaches(from, to, newCg, knownReachability)) { newCg.addEdge(from, to, types); } } return newCg; }, /** * Computes the call graph from the given dataflow graph. * @see {@link CallGraph} - for details * @see {@link CallGraph.computeSubCallGraph} - to extract sub call graphs * @see {@link CallGraph.dropTransitiveEdges} - to reduce the call graph by dropping transitive edges */ compute(graph) { const result = new graph_1.DataflowGraph(graph.idMap); const state = { visited: new Set(), potentials: [] }; for (const [, vert] of graph.vertices(false)) { if (vert.tag === vertex_1.VertexType.FunctionCall) { processCall(vert, undefined, graph, result, state); } else if (vert.tag === vertex_1.VertexType.FunctionDefinition) { processFunctionDefinition(vert, undefined, graph, result, state); } } for (const [from, tos] of state.potentials) { for (const to of tos) { if (!result.hasVertex(to)) { const v = graph.getVertex(to); if (v) { processUnknown(v, from, graph, result, state); if (v.tag === vertex_1.VertexType.FunctionDefinition) { processFunctionDefinition(v, from, graph, result, state); } } } else { result.addEdge(from, to, edge_1.EdgeType.Calls); } } } return result; } }; //# sourceMappingURL=call-graph.js.map