UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

255 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeCallContextQueries = executeCallContextQueries; const node_id_1 = require("../../../r-bridge/lang-4.x/ast/model/processing/node-id"); const vertex_1 = require("../../../dataflow/graph/vertex"); const edge_1 = require("../../../dataflow/graph/edge"); const extract_cfg_1 = require("../../../control-flow/extract-cfg"); const two_layer_collector_1 = require("../../two-layer-collector"); const objects_1 = require("../../../util/objects"); const identify_link_to_last_call_relation_1 = require("./identify-link-to-last-call-relation"); /* if the node is effected by nse, we have an ingoing nse edge */ function isQuoted(node, graph) { const vertex = graph.ingoingEdges(node); if (vertex === undefined) { return false; } return [...vertex.values()].some(({ types }) => (0, edge_1.edgeIncludesType)(types, edge_1.EdgeType.NonStandardEvaluation)); } function makeReport(collector) { const result = {}; for (const [kind, collected] of collector.store) { const subkinds = {}; for (const [subkind, values] of collected) { if (!Array.isArray(subkinds[subkind])) { subkinds[subkind] = []; } subkinds[subkind] ??= []; const collectIn = subkinds[subkind]; for (const value of values) { collectIn.push(value); } } result[kind] = { subkinds }; } return result; } function isSubCallQuery(query) { return 'linkTo' in query && query.linkTo !== undefined; } function exactCallNameRegex(name) { return new RegExp(`^(${name})$`); } function promoteQueryCallNames(queries) { let requiresCfg = false; const promotedQueries = queries.map(q => { if (isSubCallQuery(q)) { requiresCfg = true; return { ...q, callName: q.callNameExact ? exactCallNameRegex(q.callName) : new RegExp(q.callName), fileFilter: q.fileFilter && { ...q.fileFilter, filter: new RegExp(q.fileFilter.filter) }, linkTo: Array.isArray(q.linkTo) ? q.linkTo.map(l => ({ ...l, callName: new RegExp(l.callName) })) : { ...q.linkTo, /* we have to add another promotion layer whenever we add something without this call name */ callName: new RegExp(q.linkTo.callName) } }; } else { return { ...q, callName: q.callNameExact ? exactCallNameRegex(q.callName) : new RegExp(q.callName), fileFilter: q.fileFilter && { ...q.fileFilter, filter: new RegExp(q.fileFilter.filter) } }; } }); return { promotedQueries, requiresCfg }; } /* maybe we want to add caches to this */ function retrieveAllCallAliases(nodeId, graph) { /* we want the names of all functions called at the source id, including synonyms and returns */ const aliases = new Map(); const visited = new Set(); /* we store the current call name */ let queue = [[(0, node_id_1.recoverContent)(nodeId, graph) ?? '', nodeId]]; while (queue.length > 0) { const [str, id] = queue.shift(); if (visited.has(id)) { continue; } visited.add(id); if (id !== nodeId) { const present = aliases.get(str); if (present) { present.push(id); } else { aliases.set(str, [id]); } } const vertex = graph.get(id); if (vertex === undefined) { continue; } const [info, outgoing] = vertex; if (info.tag !== vertex_1.VertexType.FunctionCall) { const x = [...outgoing] .filter(([, { types }]) => (0, edge_1.edgeIncludesType)(types, edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall)) .map(([t]) => [(0, node_id_1.recoverContent)(t, graph) ?? '', t]); /** only follow defined-by and reads */ queue = queue.concat(x); continue; } let track = edge_1.EdgeType.Calls | edge_1.EdgeType.Reads | edge_1.EdgeType.DefinedBy | edge_1.EdgeType.DefinedByOnCall; if (id !== nodeId) { track |= edge_1.EdgeType.Returns; } const out = [...outgoing] .filter(([, e]) => (0, edge_1.edgeIncludesType)(e.types, track) && (nodeId !== id || !(0, edge_1.edgeIncludesType)(e.types, edge_1.EdgeType.Argument))) .map(([t]) => t); for (const call of out) { queue.push([(0, node_id_1.recoverContent)(call, graph) ?? (0, node_id_1.recoverContent)(id, graph) ?? '', call]); } } return aliases; } function removeIdenticalDuplicates(collector) { for (const [, collected] of collector.store) { for (const [subkind, values] of collected) { const seen = new Set(); const newValues = values.filter(v => { const str = JSON.stringify(v); if (seen.has(str)) { return false; } seen.add(str); return true; }); collected.set(subkind, newValues); } } } function doesFilepathMatch(file, filter) { if (filter === undefined) { return true; } if (file === undefined) { return filter.includeUndefinedFiles ?? true; } return filter.filter.test(file); } function isParameterDefaultValue(nodeId, ast) { let node = ast.idMap.get(nodeId); while (node !== undefined) { if (node.info.role === "param-value" /* RoleInParent.ParameterDefaultValue */) { return true; } node = node.info.parent ? ast.idMap.get(node.info.parent) : undefined; } return false; } /** * Multi-stage call context query resolve. * * 1. Resolve all calls in the DF graph that match the respective {@link DefaultCallContextQueryFormat#callName} regex. * 2. If there is an alias attached, consider all call traces. * 3. Identify their respective call targets, if {@link DefaultCallContextQueryFormat#callTargets} is set to be non-any. * This happens during the main resolution! * 4. Attach `linkTo` calls to the respective calls. */ function executeCallContextQueries({ dataflow: { graph }, ast }, queries) { /* omit performance page load */ const now = Date.now(); /* the node id and call targets if present */ const initialIdCollector = new two_layer_collector_1.TwoLayerCollector(); /* promote all strings to regex patterns */ const { promotedQueries, requiresCfg } = promoteQueryCallNames(queries); let cfg = undefined; if (requiresCfg) { cfg = (0, extract_cfg_1.extractCfg)(ast, graph, []); } const queriesWhichWantAliases = promotedQueries.filter(q => q.includeAliases); for (const [nodeId, info] of graph.vertices(true)) { if (info.tag !== vertex_1.VertexType.FunctionCall) { continue; } /* if we have a vertex, and we check for aliased calls, we want to know if we define this as desired! */ if (queriesWhichWantAliases.length > 0) { /* * yes, we make an expensive call target check, we can probably do a lot of optimization here, e.g., * by checking all of these queries would be satisfied otherwise, * in general, we first want a call to happen, i.e., trace the called targets of this! */ const targets = retrieveAllCallAliases(nodeId, graph); for (const [l, ids] of targets.entries()) { for (const query of queriesWhichWantAliases) { if (query.callName.test(l)) { initialIdCollector.add(query.kind ?? '.', query.subkind ?? '.', (0, objects_1.compactRecord)({ id: nodeId, name: info.name, aliasRoots: ids })); } } } } for (const query of promotedQueries.filter(q => !q.includeAliases && q.callName.test(info.name))) { const file = ast.idMap.get(nodeId)?.info.file; if (!doesFilepathMatch(file, query.fileFilter)) { continue; } let targets = undefined; if (query.callTargets) { targets = (0, identify_link_to_last_call_relation_1.satisfiesCallTargets)(nodeId, graph, query.callTargets); if (targets === 'no') { continue; } } if (isQuoted(nodeId, graph)) { /* if the call is quoted, we do not want to link to it */ continue; } else if (query.ignoreParameterValues && isParameterDefaultValue(nodeId, ast)) { continue; } let linkedIds = undefined; if (cfg && isSubCallQuery(query)) { const linked = Array.isArray(query.linkTo) ? query.linkTo : [query.linkTo]; for (const link of linked) { /* if we have a linkTo query, we have to find the last call */ const lastCall = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelation)(nodeId, cfg.graph, graph, link); if (lastCall) { linkedIds ??= new Set(); for (const l of lastCall) { if (link.attachLinkInfo) { linkedIds.add({ id: l, info: link.attachLinkInfo }); } else { linkedIds.add(l); } } } } } initialIdCollector.add(query.kind ?? '.', query.subkind ?? '.', (0, objects_1.compactRecord)({ id: nodeId, name: info.name, calls: targets, linkedIds: linkedIds ? [...linkedIds] : undefined })); } } removeIdenticalDuplicates(initialIdCollector); return { '.meta': { timing: Date.now() - now, }, kinds: makeReport(initialIdCollector) }; } //# sourceMappingURL=call-context-query-executor.js.map