UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

212 lines 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Enrichments = exports.Enrichment = void 0; exports.enrichmentContent = enrichmentContent; exports.enrichElement = enrichElement; const objects_1 = require("../../util/objects"); const vertex_1 = require("../../dataflow/graph/vertex"); const assert_1 = require("../../util/assert"); const node_id_1 = require("../../r-bridge/lang-4.x/ast/model/processing/node-id"); const cfg_simplification_1 = require("../../control-flow/cfg-simplification"); const call_context_query_executor_1 = require("../../queries/catalog/call-context-query/call-context-query-executor"); const cfg_kind_1 = require("../../project/cfg-kind"); const identify_link_to_last_call_relation_1 = require("../../queries/catalog/call-context-query/identify-link-to-last-call-relation"); const identifier_1 = require("../../dataflow/environments/identifier"); const df_helper_1 = require("../../dataflow/graph/df-helper"); const model_1 = require("../../r-bridge/lang-4.x/ast/model/model"); /** * An enumeration that stores the names of the available enrichments that can be applied to a set of search elements. * See {@link FlowrSearchBuilder.with} for more information on how to apply enrichments. */ var Enrichment; (function (Enrichment) { Enrichment["CallTargets"] = "call-targets"; Enrichment["LastCall"] = "last-call"; Enrichment["CfgInformation"] = "cfg-information"; Enrichment["Roxygen"] = "roxygen"; Enrichment["QueryData"] = "query-data"; })(Enrichment || (exports.Enrichment = Enrichment = {})); /** * The registry of enrichments that are currently supported by the search. * See {@link FlowrSearchBuilder.with} for more information on how to apply enrichments. */ exports.Enrichments = { [Enrichment.CallTargets]: { enrichSearch: async (_search, data, _args, prev) => prev ?? { dfg: await data.dataflow(), ast: await data.normalize() }, enrichElement: async (e, s, analyzer, args, prev) => { // we don't resolve aliases here yet! const content = { targets: [] }; const shared = s.enrichmentContent(Enrichment.CallTargets); const df = shared?.dfg ?? await analyzer.dataflow(); const n = shared?.ast ?? await analyzer.normalize(); const callVertex = df.graph.getVertex(e.node.info.id); if (vertex_1.FunctionCallVertex.is(callVertex)) { const origins = df_helper_1.Dataflow.origin(df.graph, callVertex.id); if (!origins || origins.length === 0) { const name = (0, node_id_1.recoverName)(callVertex.id, n.idMap); // we don't have origin information here, so pass undefined content.targets = [qualifyIdentifier(undefined, name)]; } else { // find call targets in user code (which have ids!) content.targets = origins.map(o => { switch (o.type) { case 2 /* OriginType.FunctionCallOrigin */: { if (node_id_1.NodeId.isBuiltIn(o.id)) { // a built-in target (e.g. a materialized package export from `library()`) has no // user-code node, so surface it as a built-in identifier (see `onlyBuiltin` below) const name = (0, node_id_1.recoverName)(o.id, n.idMap); return qualifyIdentifier([o], name) ?? String(o.id); } else { return { node: n.idMap.get(o.id) }; } } case 3 /* OriginType.BuiltInFunctionOrigin */: return qualifyIdentifier([o], o.fn.name); default: return undefined; } }).filter(assert_1.isNotUndefined); if (content.targets.length === 0) { const name = (0, node_id_1.recoverName)(callVertex.id, n.idMap); content.targets = [qualifyIdentifier(origins, name)]; } } } // keep only calls whose targets are all built-in; library/package exports arrive as an identifier // targets and count as built-in, a target with a `node` is user code and disqualifies the call if (args?.onlyBuiltin && content.targets.some(t => typeof t !== 'string')) { content.targets = []; } if (prev) { content.targets.push(...prev.targets); } return content; function qualifyIdentifier(origins, name) { if (args?.qualifyNames === undefined || args.qualifyNames) { const qualified = identifier_1.Identifier.toQualified(origins, name); if (qualified !== undefined) { return identifier_1.Identifier.toString(qualified); } } return name; } }, // as built-in call target enrichments are not nodes, we don't return them as part of the mapper! mapper: ({ targets }) => targets.map(t => t).filter(t => t.node !== undefined) }, [Enrichment.LastCall]: { enrichSearch: async (_search, data, _args, prev) => prev ?? { dfg: await data.dataflow(), ast: await data.normalize(), cfg: await data.controlflow(undefined, cfg_kind_1.CfgKind.Quick) }, enrichElement: async (e, s, analyzer, args, prev) => { (0, assert_1.guard)(args && args.length, `${Enrichment.LastCall} enrichment requires at least one argument`); const content = prev ?? { linkedIds: [] }; const shared = s.enrichmentContent(Enrichment.LastCall); const df = (shared?.dfg ?? await analyzer.dataflow()).graph; const vertex = df.getVertex(e.node.info.id); if (vertex_1.FunctionCallVertex.is(vertex)) { const n = shared?.ast ?? await analyzer.normalize(); const cfg = (shared?.cfg ?? await analyzer.controlflow(undefined, cfg_kind_1.CfgKind.Quick)).graph; for (const arg of args) { const lastCalls = (0, identify_link_to_last_call_relation_1.identifyLinkToLastCallRelationSync)(vertex.id, cfg, df, { ...arg, callName: (0, call_context_query_executor_1.promoteCallName)(arg.callName), type: 'link-to-last-call', }); for (const lastCall of lastCalls) { content.linkedIds.push({ node: n.idMap.get(lastCall) }); } } } return content; }, mapper: ({ linkedIds }) => linkedIds }, [Enrichment.CfgInformation]: { enrichElement: (e, search, _data, _args, prev) => { const searchContent = search.enrichmentContent(Enrichment.CfgInformation); return { ...prev, isRoot: searchContent.cfg.graph.rootIds().has(e.node.info.id), isReachable: searchContent.reachableNodes?.has(e.node.info.id) }; }, enrichSearch: async (_search, data, args, prev) => { args = { forceRefresh: false, checkReachable: false, simplificationPasses: cfg_simplification_1.DefaultCfgSimplificationOrder, ...args }; // short-circuit if we already have a cfg stored (and the reachability info if requested) if (!args.forceRefresh && prev?.cfg && (!args.checkReachable || prev.reachableNodes)) { return prev; } const content = { ...prev, cfg: await data.controlflow(args.simplificationPasses, cfg_kind_1.CfgKind.WithDataflow), }; if (args.checkReachable) { content.reachableNodes = (0, cfg_simplification_1.cfgFindAllReachable)(content.cfg); } return content; } }, [Enrichment.Roxygen]: { enrichSearch: async (_search, data, _args, prev) => prev ?? { ast: await data.normalize() }, enrichElement: async (e, search, analyzer, _args, prev) => { const content = (prev ?? { documentation: [], tags: {} }); const shared = search.enrichmentContent(Enrichment.Roxygen); const normalize = shared?.ast ?? await analyzer.normalize(); const roxygen = model_1.RNode.documentation(e.node.info.id, normalize.idMap); if (roxygen !== undefined) { const comments = (Array.isArray(roxygen) ? roxygen : [roxygen]); content.documentation.push(...comments); for (const comment of comments) { content.tags[comment.type] ??= []; content.tags[comment.type].push(comment); } } return content; } }, [Enrichment.QueryData]: { // the query data enrichment is just a "pass-through" that passes the query data to the underlying search enrichElement: (_e, _search, _data, args, prev) => (args ?? prev), enrichSearch: (_search, _data, args, prev) => (0, objects_1.deepMergeObject)(prev, args) } }; /** * Returns the content of the given enrichment type from a {@link FlowrSearchElement}. * If the search element is not enriched with the given enrichment, `undefined` is returned. * @param e - The search element whose enrichment content should be retrieved. * @param enrichment - The enrichment content, if present, else `undefined`. */ function enrichmentContent(e, enrichment) { return e?.enrichments?.[enrichment]; } /** * Enriches the given search element with the given enrichment type, using the provided analysis data. */ async function enrichElement(e, s, analyzer, enrichment, args) { const enrichmentData = exports.Enrichments[enrichment]; const prev = e?.enrichments; return { ...e, enrichments: { ...prev ?? {}, [enrichment]: await enrichmentData.enrichElement?.(e, s, analyzer, args, prev?.[enrichment]) } }; } //# sourceMappingURL=search-enrichers.js.map